From cb5b4ab9485a32f3346fa6236897e82f76a51bb2 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Wed, 10 Jun 2020 20:02:12 +0200 Subject: [PATCH 001/947] Initial commit of adding 'files' section to representation - WIP All representation integrated in DB should contain all attached resources. This will be used in future synchronization implementation. --- pype/plugins/global/publish/integrate_new.py | 139 ++++++++++++++++++- 1 file changed, 134 insertions(+), 5 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index bd908901cc..60809b864f 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -91,18 +91,28 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): default_template_name = "publish" template_name_profiles = None + integrated_file_sizes = {} # file_url : file_size + def process(self, instance): if [ef for ef in self.exclude_families if instance.data["family"] in ef]: return - self.register(instance) + self.log.info("IntegrateAssetNew.process:") + import json + self.log.info("instance: {}".format(json.dumps(instance.__dict__, default=str))) - self.log.info("Integrating Asset in to the database ...") - self.log.info("instance.data: {}".format(instance.data)) - if instance.data.get('transfer', True): - self.integrate(instance) + try: + self.register(instance) + self.log.info("Integrated Asset in to the database ...") + self.log.info("instance.data: {}".format(instance.data)) + except Exception: + # clean destination + # !TODO fix exceptions.WindowsError + e = sys.exc_info()[0] + self.log.critical("Error when registering {}".format(e)) + self.clean_destination_files(self.integrated_file_sizes) def register(self, instance): # Required environment variables @@ -472,6 +482,18 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_padding_exp % int(repre.get("frameStart")) ) + if instance.data.get('transfer', True): + # could throw exception, will be caught in 'process' + # all integration to DB is being done together lower, so no rollback needed + self.log.info("Integrating source files to destination ...") + self.integrated_file_sizes = self.integrate(instance) + self.log.debug("Integrated files {}".format(self.integrated_file_sizes)) + #TODO instance.data["transfers"].remove([src, dst]) # array needs to be changed to tuple + + # get 'files' information for representation and all attached resources + self.log.debug("Preparing files information ..") + representation["files"] = self.get_files_info(dst, instance, self.integrated_file_sizes) + self.log.debug("__ representation: {}".format(representation)) destination_list.append(dst) self.log.debug("__ destination_list: {}".format(destination_list)) @@ -509,7 +531,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Args: instance: the instance to integrate + Returns: + integrated_file_sizes: dictionary of destination file url and its size in bytes """ + integrated_file_sizes = {} # store destination url and size for reporting and rollback transfers = instance.data.get("transfers", list()) for src, dest in transfers: @@ -519,6 +544,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): transfers = instance.data.get("transfers", list()) for src, dest in transfers: self.copy_file(src, dest) + if os.path.exists(dest): + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) + # Produce hardlinked copies # Note: hardlink can only be produced between two files on the same @@ -529,6 +558,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for src, dest in hardlinks: self.log.debug("Hardlinking file .. {} -> {}".format(src, dest)) self.hardlink_file(src, dest) + if os.path.exists(dest): + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) + + return integrated_file_sizes def copy_file(self, src, dst): """ Copy given source to destination @@ -764,3 +798,98 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ).format(family, task_name, template_name)) return template_name + + def get_files_info(self, dst, instance, integrated_file_sizes): + """ Prepare 'files' portion of representation to store all attached files (representatio, textures, json etc.) + This information is used in synchronization of mentioned files + + Args: + dst: destination path for representation + instance: the instance to integrate + Returns: + files: list of dictionaries with representation and all resources + """ + self.log.debug("get_files_info:") + # add asset and all resources to 'files' + files = [] + # file info for representation - should be always + rec = self.prepare_file_info(dst, integrated_file_sizes[dst], 'temphash') # TODO + files.append(rec) + + # file info for resources + resource_files = self.get_resource_files_info(instance, integrated_file_sizes) + if resource_files: # do not append empty list + files.extend(resource_files) + + return files + + def get_resource_files_info(self, instance, integrated_file_sizes): + """ Prepare 'files' portion for attached resources, + could be empty if no resources (like textures) present + + Arguments: + instance: the current instance being published + Returns: + output_resources: array of dictionaries to be added to 'files' key in representation + """ + # TODO check if sourceHashes is viable or transfers (they are updated during loop though) + resources = instance.data.get("sourceHashes", {}) + self.log.debug("get_resource_files_info: {}".format(resources)) + output_resources = [] + for resource_info, resource_path in resources.items(): + # TODO - hash or use self.integrated_file_size + file_name,file_time,file_size,file_args = resource_info.split("|") + output_resources.append(self.prepare_file_info(resource_path, file_size, 'temphash')) + + return output_resources + + def prepare_file_info(self, path, size = None, hash = None, sites = None): + """ Prepare information for one file (asset or resource) + + Arguments: + path: destination url of published file + size(optional): size of file in bytes + hash(optional): hash of file for synchronization validation + sites(optional): array of published locations, ['studio'] by default, + expected ['studio', 'site1', 'gdrive1'] + Returns: + rec: dictionary with filled info + """ + + rec = { # TODO update collection step to extend with necessary values + "_id": io.ObjectId(), + "path": path + } + if size: + rec["size"] = size + + if hash: + rec["hash"] = hash + + if sites: + rec["sites"] = sites + else: + rec["sites"] = ["studio"] + + self.log.debug("prepare_file_info: {}".format(rec)) + + return rec + + def clean_destination_files(self, integrated_file_sizes): + """ Clean destination files + Called when error happened during integrating to DB or to disk + Used to clean unwanted files + + Arguments: + integrated_file_sizes: disctionary of uploaded files, file urls as keys + """ + if integrated_file_sizes: + for file_url, file_size in integrated_file_sizes.items(): + try: + self.log.debug("Removing file...{}".format(file_url)) + os.remove(file_url) # needs to be changed to Factory when sites implemented + except FileNotFoundError: + pass # file not there, nothing to delete + except OSError as e: + self.log.critical("Cannot remove file {}".format(file_url)) + self.log.critical(e) \ No newline at end of file From f1ea60f98312627af0bdc9d24a938d0c74315c94 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 11 Jun 2020 09:43:57 +0200 Subject: [PATCH 002/947] Fix - removed double copy of already copied file Fix - remove double creation of hardlink resulting in WindowError --- pype/plugins/global/publish/integrate_new.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 60809b864f..740de4d930 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -544,9 +544,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): transfers = instance.data.get("transfers", list()) for src, dest in transfers: self.copy_file(src, dest) - if os.path.exists(dest): - # TODO needs to be updated during site implementation - integrated_file_sizes[dest] = os.path.getsize(dest) + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) + # already copied, delete from transfers to limit double copy TODO double check + instance.data.get("transfers", list()).remove([src, dest]) # Produce hardlinked copies @@ -557,10 +558,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): hardlinks = instance.data.get("hardlinks", list()) for src, dest in hardlinks: self.log.debug("Hardlinking file .. {} -> {}".format(src, dest)) - self.hardlink_file(src, dest) - if os.path.exists(dest): - # TODO needs to be updated during site implementation - integrated_file_sizes[dest] = os.path.getsize(dest) + if not os.path.exists(dest): + self.hardlink_file(src, dest) + + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) return integrated_file_sizes From 91c3f479239779da46cae5349b06b2408df83efd Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 11 Jun 2020 18:32:36 +0200 Subject: [PATCH 003/947] Added creation of temporary files in destination first Fix - integrated_file_sizes got carried over Fix - create orig_transfers to fall back to original transfers Fix - updated get_files_info logic - using transfers and hardlinks Fix - added rootless_path into representation Fix - updated file handling if errors --- pype/plugins/global/publish/integrate_new.py | 182 +++++++++++-------- 1 file changed, 110 insertions(+), 72 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 740de4d930..3f906e3455 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -91,28 +91,30 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): default_template_name = "publish" template_name_profiles = None - integrated_file_sizes = {} # file_url : file_size + integrated_file_sizes = {} # file_url : file_size of all published and uploaded files + + TMP_FILE_EXT = 'tmp' # suffix to denote temporary files, use without '.' def process(self, instance): - + self.integrated_file_sizes = {} if [ef for ef in self.exclude_families if instance.data["family"] in ef]: return self.log.info("IntegrateAssetNew.process:") import json - self.log.info("instance: {}".format(json.dumps(instance.__dict__, default=str))) + self.log.debug("instance: {}".format(json.dumps(instance.__dict__, default=str))) try: self.register(instance) self.log.info("Integrated Asset in to the database ...") self.log.info("instance.data: {}".format(instance.data)) - except Exception: + self.handle_destination_files(self.integrated_file_sizes, instance, 'finalize') + except Exception as e: # clean destination - # !TODO fix exceptions.WindowsError - e = sys.exc_info()[0] - self.log.critical("Error when registering {}".format(e)) - self.clean_destination_files(self.integrated_file_sizes) + self.log.critical("Error when registering", exc_info=True) + self.handle_destination_files(self.integrated_file_sizes, instance, 'remove') + raise def register(self, instance): # Required environment variables @@ -273,13 +275,20 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): representations = [] destination_list = [] + orig_transfers = [] if 'transfers' not in instance.data: instance.data['transfers'] = [] + else: + orig_transfers = list(instance.data['transfers']) template_name = self.template_name_from_instance(instance) published_representations = {} for idx, repre in enumerate(instance.data["representations"]): + # reset transfers for next representation + # instance.data['transfers'] is used as a global variable in current codebase + instance.data['transfers'] = list(orig_transfers) + published_files = [] # create template data for Anatomy @@ -482,17 +491,22 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_padding_exp % int(repre.get("frameStart")) ) - if instance.data.get('transfer', True): + # any file that should be physically copied is expected in 'transfers' or 'hardlinks' + # both have same interface [[source_url, destination_url], [source_url...]] + if instance.data.get('transfers', False) or instance.data.get('hardlinks', False): # could throw exception, will be caught in 'process' # all integration to DB is being done together lower, so no rollback needed - self.log.info("Integrating source files to destination ...") - self.integrated_file_sizes = self.integrate(instance) + self.log.debug("Integrating source files to destination ...") + self.integrated_file_sizes.update(self.integrate(instance)) self.log.debug("Integrated files {}".format(self.integrated_file_sizes)) - #TODO instance.data["transfers"].remove([src, dst]) # array needs to be changed to tuple + + import random + if random.choice([True, False, True, True]): + raise Exception("Monkey attack!!!") # get 'files' information for representation and all attached resources self.log.debug("Preparing files information ..") - representation["files"] = self.get_files_info(dst, instance, self.integrated_file_sizes) + representation["files"] = self.get_files_info(instance, self.integrated_file_sizes) self.log.debug("__ representation: {}".format(representation)) destination_list.append(dst) @@ -535,20 +549,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): integrated_file_sizes: dictionary of destination file url and its size in bytes """ integrated_file_sizes = {} # store destination url and size for reporting and rollback - transfers = instance.data.get("transfers", list()) - + transfers = list(instance.data.get("transfers", list())) for src, dest in transfers: if os.path.normpath(src) != os.path.normpath(dest): + dest = self.get_dest_temp_url(dest) self.copy_file(src, dest) - - transfers = instance.data.get("transfers", list()) - for src, dest in transfers: - self.copy_file(src, dest) - # TODO needs to be updated during site implementation - integrated_file_sizes[dest] = os.path.getsize(dest) - # already copied, delete from transfers to limit double copy TODO double check - instance.data.get("transfers", list()).remove([src, dest]) - + # TODO needs to be updated during site implementation + integrated_file_sizes[dest] = os.path.getsize(dest) # Produce hardlinked copies # Note: hardlink can only be produced between two files on the same @@ -557,6 +564,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # to ensure publishes remain safe and non-edited. hardlinks = instance.data.get("hardlinks", list()) for src, dest in hardlinks: + dest = self.get_dest_temp_url(dest) self.log.debug("Hardlinking file .. {} -> {}".format(src, dest)) if not os.path.exists(dest): self.hardlink_file(src, dest) @@ -692,16 +700,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] - success, rootless_path = ( - anatomy.find_root_template_from_path(source) - ) - if success: - source = rootless_path - else: - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(source)) + source = self.get_rootless_path(anatomy, source) self.log.debug("Source: {}".format(source)) version_data = { @@ -801,55 +800,75 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return template_name - def get_files_info(self, dst, instance, integrated_file_sizes): - """ Prepare 'files' portion of representation to store all attached files (representatio, textures, json etc.) - This information is used in synchronization of mentioned files - Args: - dst: destination path for representation - instance: the instance to integrate - Returns: - files: list of dictionaries with representation and all resources + def get_rootless_path(self, anatomy, path): + """ Returns, if possible, path without absolute portion from host (eg. 'c:\' or '/opt/..') + This information is host dependent and shouldn't be captured. + Example: + 'c:/projects/MyProject1/Assets/publish...' > '{root}/MyProject1/Assets...' + + Args: + anatomy: anatomy part from instance + path: path (absolute) + Returns: + path: modified path if possible, or unmodified path + warning logged """ - self.log.debug("get_files_info:") - # add asset and all resources to 'files' - files = [] - # file info for representation - should be always - rec = self.prepare_file_info(dst, integrated_file_sizes[dst], 'temphash') # TODO - files.append(rec) + success, rootless_path = ( + anatomy.find_root_template_from_path(path) + ) + if success: + path = rootless_path + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(path)) + return path - # file info for resources - resource_files = self.get_resource_files_info(instance, integrated_file_sizes) - if resource_files: # do not append empty list - files.extend(resource_files) - - return files - - def get_resource_files_info(self, instance, integrated_file_sizes): - """ Prepare 'files' portion for attached resources, - could be empty if no resources (like textures) present + def get_files_info(self, instance, integrated_file_sizes): + """ Prepare 'files' portion for attached resources and main asset. + Combining records from 'transfers' and 'hardlinks' parts from instance. + All attached resources should be added, currently without Context info. Arguments: instance: the current instance being published + integrated_file_sizes: dictionary of destination path (absolute) and its file size Returns: output_resources: array of dictionaries to be added to 'files' key in representation """ - # TODO check if sourceHashes is viable or transfers (they are updated during loop though) - resources = instance.data.get("sourceHashes", {}) - self.log.debug("get_resource_files_info: {}".format(resources)) + resources = list(instance.data.get("transfers", [])) + resources.extend(list(instance.data.get("hardlinks", []))) + + self.log.debug("get_resource_files_info.resources: {}".format(resources)) + output_resources = [] - for resource_info, resource_path in resources.items(): + anatomy = instance.context.data["anatomy"] + for src, dest in resources: # TODO - hash or use self.integrated_file_size - file_name,file_time,file_size,file_args = resource_info.split("|") - output_resources.append(self.prepare_file_info(resource_path, file_size, 'temphash')) + path = self.get_rootless_path(anatomy, dest) + dest = self.get_dest_temp_url(dest) + output_resources.append(self.prepare_file_info(path, integrated_file_sizes[dest], 'temphash')) return output_resources + def get_dest_temp_url(self, dest): + """ Enhance destination path with TMP_FILE_EXT to denote temporary file. + Temporary files will be renamed after successful registration into DB and full copy to destination + + Arguments: + dest: destination url of published file (absolute) + Returns: + dest: destination path + '.TMP_FILE_EXT' + """ + if self.TMP_FILE_EXT and '.{}'.format(self.TMP_FILE_EXT) not in dest: + dest += '.{}'.format(self.TMP_FILE_EXT) + return dest + def prepare_file_info(self, path, size = None, hash = None, sites = None): """ Prepare information for one file (asset or resource) Arguments: - path: destination url of published file + path: destination url of published file (rootless) size(optional): size of file in bytes hash(optional): hash of file for synchronization validation sites(optional): array of published locations, ['studio'] by default, @@ -873,25 +892,44 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: rec["sites"] = ["studio"] - self.log.debug("prepare_file_info: {}".format(rec)) - return rec - def clean_destination_files(self, integrated_file_sizes): + def handle_destination_files(self, integrated_file_sizes, instance, mode): """ Clean destination files Called when error happened during integrating to DB or to disk Used to clean unwanted files Arguments: integrated_file_sizes: disctionary of uploaded files, file urls as keys + instance: processed instance - for publish directories + mode: 'remove' - clean files,'finalize' - rename files, remove TMP_FILE_EXT suffix denoting temp file """ if integrated_file_sizes: for file_url, file_size in integrated_file_sizes.items(): try: - self.log.debug("Removing file...{}".format(file_url)) - os.remove(file_url) # needs to be changed to Factory when sites implemented + if mode == 'remove': + self.log.debug("Removing file...{}".format(file_url)) + os.remove(file_url) # needs to be changed to Factory when sites implemented + if mode == 'finalize': + self.log.debug("Renaming file...{}".format(file_url)) + import re + os.rename(file_url, re.sub('\.{}$'.format(self.TMP_FILE_EXT), '', file_url)) # needs to be changed to Factory when sites implemented + except FileNotFoundError: pass # file not there, nothing to delete except OSError as e: - self.log.critical("Cannot remove file {}".format(file_url)) - self.log.critical(e) \ No newline at end of file + self.log.critical("Cannot {} file {}".format(mode, file_url), exc_info=True) + raise + + if mode == 'remove': + try: + publishDir = instance.data.get('publishDir', '') + resourcesDir = instance.data.get('resourcesDir', '') + if resourcesDir: + os.remove(resourcesDir) + if publishDir: + os.remove(publishDir) + except OSError as e: + self.log.critical("Cannot remove destination directory {} or {}".format(publishDir, resourcesDir), exc_info=True) + raise + From 417358dc7fff007eec1b14efdd21ded9a7053dc4 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 11 Jun 2020 18:34:25 +0200 Subject: [PATCH 004/947] Housekeeping - removed parts for testing and debugging Removed delete of publishDir and resourceDir, probably better to keep there empty then deleting --- pype/plugins/global/publish/integrate_new.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 3f906e3455..e3e71f2dc1 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -101,10 +101,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if instance.data["family"] in ef]: return - self.log.info("IntegrateAssetNew.process:") - import json - self.log.debug("instance: {}".format(json.dumps(instance.__dict__, default=str))) - try: self.register(instance) self.log.info("Integrated Asset in to the database ...") @@ -500,10 +496,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.integrated_file_sizes.update(self.integrate(instance)) self.log.debug("Integrated files {}".format(self.integrated_file_sizes)) - import random - if random.choice([True, False, True, True]): - raise Exception("Monkey attack!!!") - # get 'files' information for representation and all attached resources self.log.debug("Preparing files information ..") representation["files"] = self.get_files_info(instance, self.integrated_file_sizes) @@ -921,15 +913,3 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.critical("Cannot {} file {}".format(mode, file_url), exc_info=True) raise - if mode == 'remove': - try: - publishDir = instance.data.get('publishDir', '') - resourcesDir = instance.data.get('resourcesDir', '') - if resourcesDir: - os.remove(resourcesDir) - if publishDir: - os.remove(publishDir) - except OSError as e: - self.log.critical("Cannot remove destination directory {} or {}".format(publishDir, resourcesDir), exc_info=True) - raise - From 2e811e448fc1265c0ba1793550dc9cba592e3cfc Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 11 Jun 2020 18:58:45 +0200 Subject: [PATCH 005/947] Taming of Hound --- pype/plugins/global/publish/integrate_new.py | 86 +++++++++++++------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index e3e71f2dc1..0fa12f965d 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -91,7 +91,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): default_template_name = "publish" template_name_profiles = None - integrated_file_sizes = {} # file_url : file_size of all published and uploaded files + # file_url : file_size of all published and uploaded files + integrated_file_sizes = {} TMP_FILE_EXT = 'tmp' # suffix to denote temporary files, use without '.' @@ -105,11 +106,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.register(instance) self.log.info("Integrated Asset in to the database ...") self.log.info("instance.data: {}".format(instance.data)) - self.handle_destination_files(self.integrated_file_sizes, instance, 'finalize') + self.handle_destination_files(self.integrated_file_sizes, + instance, 'finalize') except Exception as e: # clean destination self.log.critical("Error when registering", exc_info=True) - self.handle_destination_files(self.integrated_file_sizes, instance, 'remove') + self.handle_destination_files(self.integrated_file_sizes, + instance, 'remove') raise def register(self, instance): @@ -282,7 +285,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): published_representations = {} for idx, repre in enumerate(instance.data["representations"]): # reset transfers for next representation - # instance.data['transfers'] is used as a global variable in current codebase + # instance.data['transfers'] is used as a global variable + # in current codebase instance.data['transfers'] = list(orig_transfers) published_files = [] @@ -487,16 +491,18 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_padding_exp % int(repre.get("frameStart")) ) - # any file that should be physically copied is expected in 'transfers' or 'hardlinks' - # both have same interface [[source_url, destination_url], [source_url...]] - if instance.data.get('transfers', False) or instance.data.get('hardlinks', False): + # any file that should be physically copied is expected in + # 'transfers' or 'hardlinks' + if instance.data.get('transfers', False) or \ + instance.data.get('hardlinks', False): # could throw exception, will be caught in 'process' - # all integration to DB is being done together lower, so no rollback needed + # all integration to DB is being done together lower, + # so no rollback needed self.log.debug("Integrating source files to destination ...") self.integrated_file_sizes.update(self.integrate(instance)) self.log.debug("Integrated files {}".format(self.integrated_file_sizes)) - # get 'files' information for representation and all attached resources + # get 'files' info for representation and all attached resources self.log.debug("Preparing files information ..") representation["files"] = self.get_files_info(instance, self.integrated_file_sizes) @@ -538,9 +544,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Args: instance: the instance to integrate Returns: - integrated_file_sizes: dictionary of destination file url and its size in bytes + integrated_file_sizes: dictionary of destination file url and + its size in bytes """ - integrated_file_sizes = {} # store destination url and size for reporting and rollback + # store destination url and size for reporting and rollback + integrated_file_sizes = {} transfers = list(instance.data.get("transfers", list())) for src, dest in transfers: if os.path.normpath(src) != os.path.normpath(dest): @@ -794,16 +802,19 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def get_rootless_path(self, anatomy, path): - """ Returns, if possible, path without absolute portion from host (eg. 'c:\' or '/opt/..') + """ Returns, if possible, path without absolute portion from host + (eg. 'c:\' or '/opt/..') This information is host dependent and shouldn't be captured. Example: - 'c:/projects/MyProject1/Assets/publish...' > '{root}/MyProject1/Assets...' + 'c:/projects/MyProject1/Assets/publish...' > + '{root}/MyProject1/Assets...' Args: anatomy: anatomy part from instance path: path (absolute) Returns: - path: modified path if possible, or unmodified path + warning logged + path: modified path if possible, or unmodified path + + warning logged """ success, rootless_path = ( anatomy.find_root_template_from_path(path) @@ -812,21 +823,25 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): path = rootless_path else: self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." ).format(path)) return path def get_files_info(self, instance, integrated_file_sizes): """ Prepare 'files' portion for attached resources and main asset. - Combining records from 'transfers' and 'hardlinks' parts from instance. - All attached resources should be added, currently without Context info. + Combining records from 'transfers' and 'hardlinks' parts from + instance. + All attached resources should be added, currently without + Context info. Arguments: instance: the current instance being published - integrated_file_sizes: dictionary of destination path (absolute) and its file size + integrated_file_sizes: dictionary of destination path (absolute) + and its file size Returns: - output_resources: array of dictionaries to be added to 'files' key in representation + output_resources: array of dictionaries to be added to 'files' key + in representation """ resources = list(instance.data.get("transfers", [])) resources.extend(list(instance.data.get("hardlinks", []))) @@ -845,7 +860,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def get_dest_temp_url(self, dest): """ Enhance destination path with TMP_FILE_EXT to denote temporary file. - Temporary files will be renamed after successful registration into DB and full copy to destination + Temporary files will be renamed after successful registration + into DB and full copy to destination Arguments: dest: destination url of published file (absolute) @@ -863,7 +879,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): path: destination url of published file (rootless) size(optional): size of file in bytes hash(optional): hash of file for synchronization validation - sites(optional): array of published locations, ['studio'] by default, + sites(optional): array of published locations, ['studio'] by default expected ['studio', 'site1', 'gdrive1'] Returns: rec: dictionary with filled info @@ -889,27 +905,35 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def handle_destination_files(self, integrated_file_sizes, instance, mode): """ Clean destination files Called when error happened during integrating to DB or to disk + OR called to rename uploaded files from temporary name to final to + highlight publishing in progress/broken Used to clean unwanted files Arguments: - integrated_file_sizes: disctionary of uploaded files, file urls as keys + integrated_file_sizes: dictionary, file urls as keys, size as value instance: processed instance - for publish directories - mode: 'remove' - clean files,'finalize' - rename files, remove TMP_FILE_EXT suffix denoting temp file + mode: 'remove' - clean files, + 'finalize' - rename files, + remove TMP_FILE_EXT suffix denoting temp file """ if integrated_file_sizes: for file_url, file_size in integrated_file_sizes.items(): try: if mode == 'remove': self.log.debug("Removing file...{}".format(file_url)) - os.remove(file_url) # needs to be changed to Factory when sites implemented + os.remove(file_url) if mode == 'finalize': self.log.debug("Renaming file...{}".format(file_url)) import re - os.rename(file_url, re.sub('\.{}$'.format(self.TMP_FILE_EXT), '', file_url)) # needs to be changed to Factory when sites implemented + os.rename(file_url, + re.sub('\.{}$'.format(self.TMP_FILE_EXT), + '', + file_url) + ) except FileNotFoundError: - pass # file not there, nothing to delete - except OSError as e: - self.log.critical("Cannot {} file {}".format(mode, file_url), exc_info=True) - raise - + pass # file not there, nothing to delete + except OSError: + self.log.critical("Cannot {} file {}".format(mode, file_url) + , exc_info=True) + raise \ No newline at end of file From ae7a6555139219c0bb384192965055a0467184de Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 Jun 2020 13:03:10 +0300 Subject: [PATCH 006/947] fix(resolve): transition to new structure --- pype/hooks/resolve/prelaunch.py | 6 +- pype/hosts/resolve/lib.py | 2 +- pype/hosts/resolve/pipeline.py | 14 ++-- pype/hosts/resolve/preload_console.py | 2 +- .../resolve/utility_scripts/Pype_menu.py | 2 +- .../utility_scripts/__dev_compound_clip.py | 65 ------------------- .../resolve/utility_scripts/__test_pyblish.py | 57 ---------------- .../utility_scripts/__test_subprocess.py | 35 ---------- 8 files changed, 13 insertions(+), 170 deletions(-) delete mode 100644 pype/hosts/resolve/utility_scripts/__dev_compound_clip.py delete mode 100644 pype/hosts/resolve/utility_scripts/__test_pyblish.py delete mode 100644 pype/hosts/resolve/utility_scripts/__test_subprocess.py diff --git a/pype/hooks/resolve/prelaunch.py b/pype/hooks/resolve/prelaunch.py index bddeccf4a3..a122b87868 100644 --- a/pype/hooks/resolve/prelaunch.py +++ b/pype/hooks/resolve/prelaunch.py @@ -46,13 +46,14 @@ class ResolvePrelaunch(PypeHook): "`RESOLVE_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" f"RESOLVE_UTILITY_SCRIPTS_DIR: `{us_dir}`" ) + self.log.debug(f"-- us_dir: `{us_dir}`") # correctly format path for pre python script pre_py_sc = os.path.normpath(env.get("PRE_PYTHON_SCRIPT", "")) env["PRE_PYTHON_SCRIPT"] = pre_py_sc - + self.log.debug(f"-- pre_py_sc: `{pre_py_sc}`...") try: - __import__("pype.resolve") + __import__("pype.hosts.resolve") __import__("pyblish") except ImportError as e: @@ -62,6 +63,7 @@ class ResolvePrelaunch(PypeHook): else: # Resolve Setup integration importlib.reload(utils) + self.log.debug(f"-- utils.__file__: `{utils.__file__}`") utils.setup(env) return True diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 2576136df5..2e759ab96c 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,6 +1,6 @@ import sys from .utils import get_resolve_module -from pypeapp import Logger +from pype.api import Logger log = Logger().get_logger(__name__, "resolve") diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/pipeline.py index 967aed1436..8dfb94486b 100644 --- a/pype/hosts/resolve/pipeline.py +++ b/pype/hosts/resolve/pipeline.py @@ -6,23 +6,21 @@ import os from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish -from pypeapp import Logger +import pype +from pype.api import Logger log = Logger().get_logger(__name__, "resolve") # self = sys.modules[__name__] AVALON_CONFIG = os.environ["AVALON_CONFIG"] -PARENT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.dirname(PARENT_DIR) -PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") -LOAD_PATH = os.path.join(PLUGINS_DIR, "resolve", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "resolve", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "resolve", "inventory") +LOAD_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "load") +CREATE_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "create") +INVENTORY_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "inventory") PUBLISH_PATH = os.path.join( - PLUGINS_DIR, "resolve", "publish" + pype.PLUGINS_DIR, "resolve", "publish" ).replace("\\", "/") AVALON_CONTAINERS = ":AVALON_CONTAINERS" diff --git a/pype/hosts/resolve/preload_console.py b/pype/hosts/resolve/preload_console.py index ea1bd4f180..58975777b8 100644 --- a/pype/hosts/resolve/preload_console.py +++ b/pype/hosts/resolve/preload_console.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import time from pype.hosts.resolve.utils import get_resolve_module -from pypeapp import Logger +from pype.api import Logger log = Logger().get_logger(__name__, "resolve") diff --git a/pype/hosts/resolve/utility_scripts/Pype_menu.py b/pype/hosts/resolve/utility_scripts/Pype_menu.py index 1f5cd36277..230a7a80f0 100644 --- a/pype/hosts/resolve/utility_scripts/Pype_menu.py +++ b/pype/hosts/resolve/utility_scripts/Pype_menu.py @@ -3,7 +3,7 @@ import sys import avalon.api as avalon import pype -from pypeapp import Logger +from pype.api import Logger log = Logger().get_logger(__name__) diff --git a/pype/hosts/resolve/utility_scripts/__dev_compound_clip.py b/pype/hosts/resolve/utility_scripts/__dev_compound_clip.py deleted file mode 100644 index fe47008c70..0000000000 --- a/pype/hosts/resolve/utility_scripts/__dev_compound_clip.py +++ /dev/null @@ -1,65 +0,0 @@ -#! python3 -# -*- coding: utf-8 -*- - - -# convert clip def -def convert_clip(timeline=None): - """Convert timeline item (clip) into compound clip pype container - - Args: - timeline (MediaPool.Timeline): Object of timeline - - Returns: - bool: `True` if success - - Raises: - Exception: description - - """ - pass - - -# decorator function create_current_timeline_media_bin() -def create_current_timeline_media_bin(timeline=None): - """Convert timeline item (clip) into compound clip pype container - - Args: - timeline (MediaPool.Timeline): Object of timeline - - Returns: - bool: `True` if success - - Raises: - Exception: description - - """ - pass - - -# decorator function get_selected_track_items() -def get_selected_track_items(): - """Convert timeline item (clip) into compound clip pype container - - Args: - timeline (MediaPool.Timeline): Object of timeline - - Returns: - bool: `True` if success - - Raises: - Exception: description - - """ - print("testText") - - -# PypeCompoundClip() class -class PypeCompoundClip(object): - """docstring for .""" - - def __init__(self, arg): - super(self).__init__() - self.arg = arg - - def create_compound_clip(self): - pass diff --git a/pype/hosts/resolve/utility_scripts/__test_pyblish.py b/pype/hosts/resolve/utility_scripts/__test_pyblish.py deleted file mode 100644 index a6fe991025..0000000000 --- a/pype/hosts/resolve/utility_scripts/__test_pyblish.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import sys -import pype -import importlib -import pyblish.api -import pyblish.util -import avalon.api -from avalon.tools import publish -from pypeapp import Logger - -log = Logger().get_logger(__name__) - - -def main(env): - # Registers pype's Global pyblish plugins - pype.install() - - # Register Host (and it's pyblish plugins) - host_name = env["AVALON_APP"] - # TODO not sure if use "pype." or "avalon." for host import - host_import_str = f"pype.{host_name}" - - try: - host_module = importlib.import_module(host_import_str) - except ModuleNotFoundError: - log.error(( - f"Host \"{host_name}\" can't be imported." - f" Import string \"{host_import_str}\" failed." - )) - return False - - avalon.api.install(host_module) - - # Register additional paths - addition_paths_str = env.get("PUBLISH_PATHS") or "" - addition_paths = addition_paths_str.split(os.pathsep) - for path in addition_paths: - path = os.path.normpath(path) - if not os.path.exists(path): - continue - - pyblish.api.register_plugin_path(path) - - # Register project specific plugins - project_name = os.environ["AVALON_PROJECT"] - project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or "" - for path in project_plugins_paths.split(os.pathsep): - plugin_path = os.path.join(path, project_name, "plugins") - if os.path.exists(plugin_path): - pyblish.api.register_plugin_path(plugin_path) - - return publish.show() - - -if __name__ == "__main__": - result = main(os.environ) - sys.exit(not bool(result)) diff --git a/pype/hosts/resolve/utility_scripts/__test_subprocess.py b/pype/hosts/resolve/utility_scripts/__test_subprocess.py deleted file mode 100644 index bdc57bbf00..0000000000 --- a/pype/hosts/resolve/utility_scripts/__test_subprocess.py +++ /dev/null @@ -1,35 +0,0 @@ -#! python3 -# -*- coding: utf-8 -*- -import os -from pypeapp import execute, Logger -from pype.hosts.resolve.utils import get_resolve_module - -log = Logger().get_logger("Resolve") - -CURRENT_DIR = os.getenv("RESOLVE_UTILITY_SCRIPTS_DIR", "") -python_dir = os.getenv("PYTHON36_RESOLVE") -python_exe = os.path.normpath( - os.path.join(python_dir, "python.exe") -) - -resolve = get_resolve_module() -PM = resolve.GetProjectManager() -P = PM.GetCurrentProject() - -log.info(P.GetName()) - - -# ______________________________________________________ -# testing subprocessing Scripts -testing_py = os.path.join(CURRENT_DIR, "ResolvePageSwitcher.py") -testing_py = os.path.normpath(testing_py) -log.info(f"Testing path to script: `{testing_py}`") - -returncode = execute( - [python_exe, os.path.normpath(testing_py)], - env=dict(os.environ) -) - -# Check if output file exists -if returncode != 0: - log.error("Executing failed!") From a4d3b40136175ae299ae0306da15c62507e8d998 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 Jun 2020 13:03:38 +0300 Subject: [PATCH 007/947] feat(resolve): adding currentFile to collect project --- pype/plugins/resolve/publish/collect_host.py | 17 ----------- .../resolve/publish/collect_project.py | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 17 deletions(-) delete mode 100644 pype/plugins/resolve/publish/collect_host.py create mode 100644 pype/plugins/resolve/publish/collect_project.py diff --git a/pype/plugins/resolve/publish/collect_host.py b/pype/plugins/resolve/publish/collect_host.py deleted file mode 100644 index a5c4b0936c..0000000000 --- a/pype/plugins/resolve/publish/collect_host.py +++ /dev/null @@ -1,17 +0,0 @@ -import pyblish.api -from pype.hosts.resolve.utils import get_resolve_module - - -class CollectProject(pyblish.api.ContextPlugin): - """Collect Project object""" - - order = pyblish.api.CollectorOrder - 0.1 - label = "Collect Project" - hosts = ["resolve"] - - def process(self, context): - resolve = get_resolve_module() - PM = resolve.GetProjectManager() - P = PM.GetCurrentProject() - - self.log.info(P.GetName()) diff --git a/pype/plugins/resolve/publish/collect_project.py b/pype/plugins/resolve/publish/collect_project.py new file mode 100644 index 0000000000..aa57f93619 --- /dev/null +++ b/pype/plugins/resolve/publish/collect_project.py @@ -0,0 +1,29 @@ +import os +import pyblish.api +from pype.hosts.resolve.utils import get_resolve_module + + +class CollectProject(pyblish.api.ContextPlugin): + """Collect Project object""" + + order = pyblish.api.CollectorOrder - 0.1 + label = "Collect Project" + hosts = ["resolve"] + + def process(self, context): + exported_projet_ext = ".drp" + current_dir = os.getenv("AVALON_WORKDIR") + resolve = get_resolve_module() + PM = resolve.GetProjectManager() + P = PM.GetCurrentProject() + name = P.GetName() + + fname = name + exported_projet_ext + current_file = os.path.join(current_dir, fname) + normalised = os.path.normpath(current_file) + + context.data["project"] = P + context.data["currentFile"] = normalised + + self.log.info(name) + self.log.debug(normalised) From a908e2867ec0ac0f1b3e11cbc8b21965cae2207b Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Fri, 12 Jun 2020 20:42:58 +0200 Subject: [PATCH 008/947] Performance testing script Could be deleted later, no real functionality --- pype/tests/test_mongo_performance.py | 232 +++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 pype/tests/test_mongo_performance.py diff --git a/pype/tests/test_mongo_performance.py b/pype/tests/test_mongo_performance.py new file mode 100644 index 0000000000..6aa8e2ca43 --- /dev/null +++ b/pype/tests/test_mongo_performance.py @@ -0,0 +1,232 @@ +import pytest +import logging +from pprint import pprint +import os +import re +import random +import timeit + +import pymongo +import bson + +class TestPerformance(): + ''' + Class for testing performance of representation and their 'files' parts. + Discussion is if embedded array: + 'files' : [ {'_id': '1111', 'path':'....}, + {'_id'...}] + OR documents: + 'files' : { + '1111': {'path':'....'}, + '2222': {'path':'...'} + } + is faster. + + Current results: without additional partial index documents is 3x faster + With index is array 50x faster then document + + Partial index something like: + db.getCollection('performance_test').createIndex + ({'files._id': 1}, + {partialFilterExpresion: {'files': {'$exists': true}}) + !DIDNT work for me, had to create manually in Compass + + ''' + + MONGO_URL = 'mongodb://localhost:27017' + MONGO_DB = 'performance_test' + MONGO_COLLECTION = 'performance_test' + + inserted_ids = [] + + def __init__(self, version='array'): + ''' + It creates and fills collection, based on value of 'version'. + + :param version: 'array' - files as embedded array, + 'doc' - as document + ''' + self.client = pymongo.MongoClient(self.MONGO_URL) + self.db = self.client[self.MONGO_DB] + self.collection_name = self.MONGO_COLLECTION + + self.version = version + + if self.version != 'array': + self.collection_name = self.MONGO_COLLECTION + '_doc' + + self.collection = self.db[self.collection_name] + + self.ids = [] # for testing + self.inserted_ids = [] + + def prepare(self, no_of_records=100000): + ''' + Produce 'no_of_records' of representations with 'files' segment. + It depends on 'version' value in constructor, 'arrray' or 'doc' + :return: + ''' + print('Purging {} collection'.format(self.collection_name)) + self.collection.delete_many({}) + + id = bson.objectid.ObjectId() + + insert_recs = [] + for i in range(no_of_records): + file_id = bson.objectid.ObjectId() + file_id2 = bson.objectid.ObjectId() + file_id3 = bson.objectid.ObjectId() + + self.inserted_ids.extend([file_id, file_id2, file_id3]) + + document = {"files": self.get_files(self.version, i, + file_id, file_id2, file_id3) + , + "context": { + "subset": "workfileLookdev", + "username": "petrk", + "task": "lookdev", + "family": "workfile", + "hierarchy": "Assets", + "project": {"code": "test", "name": "Test"}, + "version": 1, + "asset": "Cylinder", + "representation": "mb", + "root": "C:/projects" + }, + "dependencies": [], + "name": "mb", + "parent": {"oid": '{}'.format(id)}, + "data": { + "path": "C:\\projects\\Test\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\v001\\test_Cylinder_workfileLookdev_v001.mb", + "template": "{root}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" + }, + "type": "representation", + "schema": "pype:representation-2.0" + } + + insert_recs.append(document) + + print('Prepared {} records in {} collection'.format(no_of_records, self.collection_name)) + id = self.collection.insert_many(insert_recs) + # TODO refactore to produce real array and not needeing ugly regex + self.collection.insert_one({"inserted_id" : self.inserted_ids}) + print('-' * 50) + + def run(self, queries=1000, loops=3): + ''' + Run X'queries' that are searching collection Y'loops' times + :param queries: how many times do ..find(...) + :param loops: loop of testing X queries + :return: None + ''' + print('Testing version {} on {}'.format(self.version, self.collection_name)) + + inserted_ids = list(self.collection.find({"inserted_id":{"$exists":True}})) + import re + self.ids = re.findall("'[0-9a-z]*'", str(inserted_ids)) + + import time + + found_cnt = 0 + for _ in range(loops): + start = time.time() + for i in range(queries): + val = random.choice(self.ids) + val = val.replace("'",'') + #print(val) + if (self.version == 'array'): + # prepared for partial index, without 'files': exists + # wont engage + found = self.collection.find_one({'files': {"$exists": True}, + 'files._id': "{}".format(val)}) + else: + key = "files.{}".format(val) + found = self.collection.find_one({key: {"$exists": True}}) + if found: + found_cnt += 1 + + end = time.time() + print('duration per loop {}'.format(end - start)) + print("found_cnt {}".format(found_cnt)) + + def get_files(self, mode, i, file_id, file_id2, file_id3): + ''' + Wrapper to decide if 'array' or document version should be used + :param mode: 'array'|'doc' + :param i: step number + :param file_id: ObjectId of first dummy file + :param file_id2: .. + :param file_id3: .. + :return: + ''' + if mode == 'array': + return self.get_files_array(i, file_id, file_id2, file_id3) + else: + return self.get_files_doc(i, file_id, file_id2, file_id3) + + def get_files_array(self, i, file_id, file_id2, file_id3): + return [ + { + "path":"c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderA_workfileLookdev_v{0:03}.mb".format(i), + "_id": '{}'.format(file_id), + "hash":"temphash", + "sites":["studio"], + "size":87236 + }, + { + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderB_workfileLookdev_v{0:03}.mb".format( + i), + "_id": '{}'.format(file_id2), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + }, + { + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderC_workfileLookdev_v{0:03}.mb".format( + i), + "_id": '{}'.format(file_id3), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } + + ] + + + def get_files_doc(self, i, file_id, file_id2, file_id3): + ret = {} + ret['{}'.format(file_id)] = { + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderA_workfileLookdev_v{0:03}.mb".format( + i), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } + + + ret['{}'.format(file_id2)] = { + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderB_workfileLookdev_v{0:03}.mb".format(i), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } + ret['{}'.format(file_id3)] = { + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderC_workfileLookdev_v{0:03}.mb".format(i), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } + + return ret + +if __name__ == '__main__': + tp = TestPerformance('array') + tp.prepare() # enable to prepare data + tp.run(1000, 3) + + print('-'*50) + + tp = TestPerformance('doc') + tp.prepare() # enable to prepare data + tp.run(1000, 3) From 700c62617814d3e9e1c1026bf3423d7cf620eee3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 Jun 2020 17:13:17 +0300 Subject: [PATCH 009/947] feat(resolve): adding create shot clip --- pype/plugins/resolve/create/create_shot_clip.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pype/plugins/resolve/create/create_shot_clip.py diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py new file mode 100644 index 0000000000..97d5663922 --- /dev/null +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -0,0 +1,15 @@ +import avalon.api +from pype.hosts import resolve + + +class CreateShotClip(avalon.api.Creator): + """Publishable clip""" + + label = "Shot" + family = "clip" + icon = "film" + defaults = ["Main"] + + def process(self): + project = resolve.get_current_project() + self.log.info(f"Project name: {project.GetName()}") From 37fff09ecdafb1d4eb3ea8af070d387415c41ec9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 17 Jun 2020 18:27:51 +0300 Subject: [PATCH 010/947] feat(resolve): wip create plugins --- pype/hosts/resolve/__init__.py | 38 ++++++++++---- pype/hosts/resolve/action.py | 6 +-- pype/hosts/resolve/lib.py | 19 +++++-- pype/hosts/resolve/pipeline.py | 51 +++++++++++++++++-- pype/hosts/resolve/plugin.py | 21 +++++++- pype/hosts/resolve/utility_scripts/test.py | 19 +++++++ pype/hosts/resolve/utils.py | 18 +++---- pype/hosts/resolve/workio.py | 5 +- .../resolve/create/create_shot_clip.py | 10 ++-- 9 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 pype/hosts/resolve/utility_scripts/test.py diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 72d6314b5e..b7e6c7dee3 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -1,17 +1,29 @@ +from .utils import ( + setup, + get_resolve_module +) + from .pipeline import ( install, uninstall, ls, containerise, publish, - launch_workfiles_app + launch_workfiles_app, + maintained_selection ) -from .utils import ( - setup, - get_resolve_module +from .lib import ( + get_project_manager, + get_current_project, + get_current_sequence, + set_project_manager_to_folder_name ) +from .menu import launch_pype_menu + +from .plugin import Creator + from .workio import ( open_file, save_file, @@ -21,12 +33,7 @@ from .workio import ( work_root ) -from .lib import ( - get_project_manager, - set_project_manager_to_folder_name -) - -from .menu import launch_pype_menu +bmd = None __all__ = [ # pipeline @@ -37,6 +44,7 @@ __all__ = [ "reload_pipeline", "publish", "launch_workfiles_app", + "maintained_selection", # utils "setup", @@ -44,16 +52,24 @@ __all__ = [ # lib "get_project_manager", + "get_current_project", + "get_current_sequence", "set_project_manager_to_folder_name", # menu "launch_pype_menu", + # plugin + "Creator", + # workio "open_file", "save_file", "current_file", "has_unsaved_changes", "file_extensions", - "work_root" + "work_root", + + # singleton with black magic resolve module + "bmd" ] diff --git a/pype/hosts/resolve/action.py b/pype/hosts/resolve/action.py index 31830937c1..a9803cef4e 100644 --- a/pype/hosts/resolve/action.py +++ b/pype/hosts/resolve/action.py @@ -21,9 +21,9 @@ class SelectInvalidAction(pyblish.api.Action): def process(self, context, plugin): try: - from pype.hosts.resolve.utils import get_resolve_module - resolve = get_resolve_module() - self.log.debug(resolve) + from . import get_project_manager + pm = get_project_manager() + self.log.debug(pm) except ImportError: raise ImportError("Current host is not Resolve") diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 2e759ab96c..25e177eb1c 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,5 +1,4 @@ import sys -from .utils import get_resolve_module from pype.api import Logger log = Logger().get_logger(__name__, "resolve") @@ -9,12 +8,26 @@ self.pm = None def get_project_manager(): + from . import bmd if not self.pm: - resolve = get_resolve_module() - self.pm = resolve.GetProjectManager() + self.pm = bmd.GetProjectManager() return self.pm +def get_current_project(): + # initialize project manager + get_project_manager() + + return self.pm.GetCurrentProject() + + +def get_current_sequence(): + # get current project + project = get_current_project() + + return project.GetCurrentTimeline() + + def set_project_manager_to_folder_name(folder_name): """ Sets context of Project manager to given folder by name. diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/pipeline.py index 8dfb94486b..91d06da274 100644 --- a/pype/hosts/resolve/pipeline.py +++ b/pype/hosts/resolve/pipeline.py @@ -2,7 +2,7 @@ Basic avalon integration """ import os -# import sys +import contextlib from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish @@ -11,8 +11,6 @@ from pype.api import Logger log = Logger().get_logger(__name__, "resolve") -# self = sys.modules[__name__] - AVALON_CONFIG = os.environ["AVALON_CONFIG"] LOAD_PATH = os.path.join(pype.PLUGINS_DIR, "resolve", "load") @@ -38,11 +36,13 @@ def install(): See the Maya equivalent for inspiration on how to implement this. """ + from . import get_resolve_module # Disable all families except for the ones we explicitly want to see family_states = [ "imagesequence", - "mov" + "mov", + "clip" ] avalon.data["familiesStateDefault"] = False avalon.data["familiesStateToggled"] = family_states @@ -57,6 +57,8 @@ def install(): avalon.register_plugin_path(avalon.Creator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + get_resolve_module() + def uninstall(): """Uninstall all tha was installed @@ -138,3 +140,44 @@ def publish(parent): """Shorthand to publish from within host""" from avalon.tools import publish return publish.show(parent) + + +@contextlib.contextmanager +def maintained_selection(): + """Maintain selection during context + + Example: + >>> with maintained_selection(): + ... node['selected'].setValue(True) + >>> print(node['selected'].value()) + False + """ + from . import get_current_project + project = get_current_project() + nodes = [] + previous_selection = None + + # deselect all nodes + reset_selection() + + try: + # do the operation + yield + finally: + # unselect all selection in case there is some + reset_selection() + # and select all previously selected nodes + if previous_selection: + try: + for n in nodes: + if n not in previous_selection: + continue + n['selected'].setValue(True) + except ValueError as e: + log.warning(e) + + +def reset_selection(): + """Deselect all selected nodes + """ + pass diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 628d4bdb26..513b9984f4 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,6 +1,7 @@ from avalon import api -# from pype.hosts.resolve import lib as drlib +from pype.hosts import resolve from avalon.vendor import qargparse +from pype.api import config def get_reference_node_parents(ref): @@ -73,3 +74,21 @@ class SequenceLoader(api.Loader): """Remove an existing `container` """ pass + + +class Creator(api.Creator): + """Creator class wrapper + """ + marker_color = "Purple" + + def __init__(self, *args, **kwargs): + super(Creator, self).__init__(*args, **kwargs) + self.presets = config.get_presets()['plugins']["resolve"][ + "create"].get(self.__class__.__name__, {}) + + # adding basic current context resolve objects + self.project = resolve.get_current_project() + self.sequence = resolve.get_current_sequence() + + # TODO: make sure no duplicity of subsets are in workfile + return diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py new file mode 100644 index 0000000000..4c43507e62 --- /dev/null +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -0,0 +1,19 @@ +#! python3 +import sys +from pype.api import Logger + +log = Logger().get_logger(__name__) + + +def main(): + import pype.hosts.resolve as bmdvr + bm = bmdvr.utils.get_resolve_module() + log.info(f"blackmagicmodule: {bm}") + +import DaVinciResolveScript as bmd +print(f"_>> bmd.scriptapp(Resolve): {bmd.scriptapp('Resolve')}") + + +if __name__ == "__main__": + result = main() + sys.exit(not bool(result)) diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/utils.py index f5add53a6b..74ce2dc98f 100644 --- a/pype/hosts/resolve/utils.py +++ b/pype/hosts/resolve/utils.py @@ -9,18 +9,16 @@ import os import shutil from pypeapp import Logger - log = Logger().get_logger(__name__, "resolve") -self = sys.modules[__name__] -self.bmd = None - def get_resolve_module(): + from pype.hosts import resolve # dont run if already loaded - if self.bmd: - return self.bmd - + if resolve.bmd: + log.info(("resolve module is assigned to " + f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) + return resolve.bmd try: """ The PYTHONPATH needs to be set correctly for this import @@ -71,8 +69,10 @@ def get_resolve_module(): ) sys.exit() # assign global var and return - self.bmd = bmd.scriptapp("Resolve") - return self.bmd + bmd = bmd.scriptapp("Resolve") + resolve.bmd = bmd + log.info(("Assigning resolve module to " + f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) def _sync_utility_scripts(env=None): diff --git a/pype/hosts/resolve/workio.py b/pype/hosts/resolve/workio.py index e1e30a8734..9d8d320a3c 100644 --- a/pype/hosts/resolve/workio.py +++ b/pype/hosts/resolve/workio.py @@ -2,8 +2,9 @@ import os from pypeapp import Logger -from .lib import ( +from . import ( get_project_manager, + get_current_project, set_project_manager_to_folder_name ) @@ -26,7 +27,7 @@ def save_file(filepath): pm = get_project_manager() file = os.path.basename(filepath) fname, _ = os.path.splitext(file) - project = pm.GetCurrentProject() + project = get_current_project() name = project.GetName() if "Untitled Project" not in name: diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 97d5663922..43a8ab0cbd 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,8 +1,7 @@ -import avalon.api from pype.hosts import resolve -class CreateShotClip(avalon.api.Creator): +class CreateShotClip(resolve.Creator): """Publishable clip""" label = "Shot" @@ -10,6 +9,9 @@ class CreateShotClip(avalon.api.Creator): icon = "film" defaults = ["Main"] + presets = None + def process(self): - project = resolve.get_current_project() - self.log.info(f"Project name: {project.GetName()}") + print(f"Project name: {self.project.GetName()}") + print(f"Sequence name: {self.sequence.GetName()}") + print(self.presets) From b1383876564297e8c710dce89653f875cbbadef2 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 18 Jun 2020 11:09:20 +0200 Subject: [PATCH 011/947] Added implementation of hash Yanked source_hash function from extract_look into lib (and api) --- pype/api.py | 21 ++++++-- pype/lib.py | 23 ++++++++- pype/plugins/global/publish/integrate_new.py | 54 ++++++++++++-------- pype/plugins/maya/publish/extract_look.py | 25 +-------- 4 files changed, 74 insertions(+), 49 deletions(-) diff --git a/pype/api.py b/pype/api.py index 2c227b5b4b..0cf2573298 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,5 +1,12 @@ -from .plugin import ( +from pypeapp import ( + Logger, + Anatomy, + project_overrides_dir_path, + config, + execute +) +from .plugin import ( Extractor, ValidatePipelineOrder, @@ -16,8 +23,6 @@ from .action import ( RepairContextAction ) -from pypeapp import Logger - from .lib import ( version_up, get_asset, @@ -26,13 +31,20 @@ from .lib import ( get_subsets, get_version_from_path, modified_environ, - add_tool_to_environment + add_tool_to_environment, + source_hash ) # Special naming case for subprocess since its a built-in method. from .lib import _subprocess as subprocess __all__ = [ + "Logger", + "Anatomy", + "project_overrides_dir_path", + "config", + "execute", + # plugin classes "Extractor", # ordering @@ -58,6 +70,7 @@ __all__ = [ "get_version_from_path", "modified_environ", "add_tool_to_environment", + "source_hash", "subprocess" ] diff --git a/pype/lib.py b/pype/lib.py index 12d4706af8..006a396720 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -15,7 +15,7 @@ from abc import ABCMeta, abstractmethod from avalon import io, pipeline import six import avalon.api -from pypeapp import config +from .api import config log = logging.getLogger(__name__) @@ -1349,3 +1349,24 @@ def ffprobe_streams(path_to_file): popen_output = popen.communicate()[0] log.debug("FFprobe output: {}".format(popen_output)) return json.loads(popen_output)["streams"] + + +def source_hash(filepath, *args): + """Generate simple identifier for a source file. + This is used to identify whether a source file has previously been + processe into the pipeline, e.g. a texture. + The hash is based on source filepath, modification time and file size. + This is only used to identify whether a specific source file was already + published before from the same location with the same modification date. + We opt to do it this way as opposed to Avalanch C4 hash as this is much + faster and predictable enough for all our production use cases. + Args: + filepath (str): The source file path. + You can specify additional arguments in the function + to allow for specific 'processing' values to be included. + """ + # We replace dots with comma because . cannot be a key in a pymongo dict. + file_name = os.path.basename(filepath) + time = str(os.path.getmtime(filepath)) + size = str(os.path.getsize(filepath)) + return "|".join([file_name, time, size] + list(args)).replace(".", ",") diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 0fa12f965d..7104611909 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -9,8 +9,9 @@ import six from pymongo import DeleteOne, InsertOne import pyblish.api -from avalon import api, io +from avalon import io from avalon.vendor import filelink +import pype.api # this is needed until speedcopy for linux is fixed if sys.platform == "win32": @@ -44,6 +45,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "frameStart" "frameEnd" 'fps' + "data": additional metadata for each representation. """ label = "Integrate Asset New" @@ -76,12 +78,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "gizmo", "source", "matchmove", - "image" + "image", "source", "assembly", "fbx", "textures", - "action" + "action", + "harmony.template" ] exclude_families = ["clip"] db_representation_context_keys = [ @@ -94,7 +97,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # file_url : file_size of all published and uploaded files integrated_file_sizes = {} - TMP_FILE_EXT = 'tmp' # suffix to denote temporary files, use without '.' + TMP_FILE_EXT = 'tmp' # suffix to denote temporary files, use without '.' def process(self, instance): self.integrated_file_sizes = {} @@ -107,12 +110,11 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.info("Integrated Asset in to the database ...") self.log.info("instance.data: {}".format(instance.data)) self.handle_destination_files(self.integrated_file_sizes, - instance, 'finalize') + 'finalize') except Exception as e: # clean destination self.log.critical("Error when registering", exc_info=True) - self.handle_destination_files(self.integrated_file_sizes, - instance, 'remove') + self.handle_destination_files(self.integrated_file_sizes, 'remove') raise def register(self, instance): @@ -394,9 +396,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): index_frame_start += 1 dst = "{0}{1}{2}".format( - dst_head, - dst_padding, - dst_tail).replace("..", ".") + dst_head, + dst_padding, + dst_tail + ).replace("..", ".") self.log.debug("destination: `{}`".format(dst)) src = os.path.join(stagingdir, src_file_name) @@ -469,13 +472,15 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre_id is None: repre_id = io.ObjectId() + data = repre.get("data") or {} + data.update({'path': dst, 'template': template}) representation = { "_id": repre_id, "schema": "pype:representation-2.0", "type": "representation", "parent": version_id, "name": repre['name'], - "data": {'path': dst, 'template': template}, + "data": data, "dependencies": instance.data.get("dependencies", "").split(), # Imprint shortcut to context @@ -500,11 +505,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # so no rollback needed self.log.debug("Integrating source files to destination ...") self.integrated_file_sizes.update(self.integrate(instance)) - self.log.debug("Integrated files {}".format(self.integrated_file_sizes)) + self.log.debug("Integrated files {}". + format(self.integrated_file_sizes)) # get 'files' info for representation and all attached resources self.log.debug("Preparing files information ..") - representation["files"] = self.get_files_info(instance, self.integrated_file_sizes) + representation["files"] = self.get_files_info( + instance, + self.integrated_file_sizes) self.log.debug("__ representation: {}".format(representation)) destination_list.append(dst) @@ -800,7 +808,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return template_name - def get_rootless_path(self, anatomy, path): """ Returns, if possible, path without absolute portion from host (eg. 'c:\' or '/opt/..') @@ -846,15 +853,21 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): resources = list(instance.data.get("transfers", [])) resources.extend(list(instance.data.get("hardlinks", []))) - self.log.debug("get_resource_files_info.resources: {}".format(resources)) + self.log.debug("get_resource_files_info.resources:{}".format(resources)) output_resources = [] anatomy = instance.context.data["anatomy"] for src, dest in resources: - # TODO - hash or use self.integrated_file_size path = self.get_rootless_path(anatomy, dest) dest = self.get_dest_temp_url(dest) - output_resources.append(self.prepare_file_info(path, integrated_file_sizes[dest], 'temphash')) + hash = pype.api.source_hash(dest) + if self.TMP_FILE_EXT and ',{}'.format(self.TMP_FILE_EXT) in hash: + hash = hash.replace(',{}'.format(self.TMP_FILE_EXT), '') + + file_info = self.prepare_file_info(path, + integrated_file_sizes[dest], + hash) + output_resources.append(file_info) return output_resources @@ -872,7 +885,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dest += '.{}'.format(self.TMP_FILE_EXT) return dest - def prepare_file_info(self, path, size = None, hash = None, sites = None): + def prepare_file_info(self, path, size=None, hash=None, sites=None): """ Prepare information for one file (asset or resource) Arguments: @@ -902,7 +915,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return rec - def handle_destination_files(self, integrated_file_sizes, instance, mode): + def handle_destination_files(self, integrated_file_sizes, mode): """ Clean destination files Called when error happened during integrating to DB or to disk OR called to rename uploaded files from temporary name to final to @@ -911,7 +924,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Arguments: integrated_file_sizes: dictionary, file urls as keys, size as value - instance: processed instance - for publish directories mode: 'remove' - clean files, 'finalize' - rename files, remove TMP_FILE_EXT suffix denoting temp file @@ -936,4 +948,4 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): except OSError: self.log.critical("Cannot {} file {}".format(mode, file_url) , exc_info=True) - raise \ No newline at end of file + raise diff --git a/pype/plugins/maya/publish/extract_look.py b/pype/plugins/maya/publish/extract_look.py index 58196433aa..6bd202093f 100644 --- a/pype/plugins/maya/publish/extract_look.py +++ b/pype/plugins/maya/publish/extract_look.py @@ -14,34 +14,13 @@ import avalon.maya from avalon import io, api import pype.api -import pype.maya.lib as lib +from pype.hosts.maya import lib # Modes for transfer COPY = 1 HARDLINK = 2 -def source_hash(filepath, *args): - """Generate simple identifier for a source file. - This is used to identify whether a source file has previously been - processe into the pipeline, e.g. a texture. - The hash is based on source filepath, modification time and file size. - This is only used to identify whether a specific source file was already - published before from the same location with the same modification date. - We opt to do it this way as opposed to Avalanch C4 hash as this is much - faster and predictable enough for all our production use cases. - Args: - filepath (str): The source file path. - You can specify additional arguments in the function - to allow for specific 'processing' values to be included. - """ - # We replace dots with comma because . cannot be a key in a pymongo dict. - file_name = os.path.basename(filepath) - time = str(os.path.getmtime(filepath)) - size = str(os.path.getsize(filepath)) - return "|".join([file_name, time, size] + list(args)).replace(".", ",") - - def find_paths_by_hash(texture_hash): # Find the texture hash key in the dictionary and all paths that # originate from it. @@ -363,7 +342,7 @@ class ExtractLook(pype.api.Extractor): args = [] if do_maketx: args.append("maketx") - texture_hash = source_hash(filepath, *args) + texture_hash = pype.api.source_hash(filepath, *args) # If source has been published before with the same settings, # then don't reprocess but hardlink from the original From 2aaae63f80470957552571d27f0e99c719e8f82f Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 18 Jun 2020 11:41:26 +0200 Subject: [PATCH 012/947] Fix - change level of logging --- pype/plugins/global/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 7104611909..a7ff3e5748 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -946,6 +946,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): except FileNotFoundError: pass # file not there, nothing to delete except OSError: - self.log.critical("Cannot {} file {}".format(mode, file_url) + self.log.error("Cannot {} file {}".format(mode, file_url) , exc_info=True) raise From eafa79af88428867881204f4ddfcf361ef300cef Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 18 Jun 2020 11:42:20 +0200 Subject: [PATCH 013/947] Taming of Hound --- pype/plugins/global/publish/integrate_new.py | 2 +- pype/tests/test_mongo_performance.py | 134 ++++++++++--------- 2 files changed, 70 insertions(+), 66 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index a7ff3e5748..c5ef2c3530 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -947,5 +947,5 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): pass # file not there, nothing to delete except OSError: self.log.error("Cannot {} file {}".format(mode, file_url) - , exc_info=True) + , exc_info=True) raise diff --git a/pype/tests/test_mongo_performance.py b/pype/tests/test_mongo_performance.py index 6aa8e2ca43..6b62f0fd1c 100644 --- a/pype/tests/test_mongo_performance.py +++ b/pype/tests/test_mongo_performance.py @@ -1,13 +1,7 @@ -import pytest -import logging -from pprint import pprint -import os -import re -import random -import timeit - import pymongo import bson +import random + class TestPerformance(): ''' @@ -57,7 +51,7 @@ class TestPerformance(): self.collection = self.db[self.collection_name] - self.ids = [] # for testing + self.ids = [] # for testing self.inserted_ids = [] def prepare(self, no_of_records=100000): @@ -73,9 +67,9 @@ class TestPerformance(): insert_recs = [] for i in range(no_of_records): - file_id = bson.objectid.ObjectId() - file_id2 = bson.objectid.ObjectId() - file_id3 = bson.objectid.ObjectId() + file_id = bson.objectid.ObjectId() + file_id2 = bson.objectid.ObjectId() + file_id3 = bson.objectid.ObjectId() self.inserted_ids.extend([file_id, file_id2, file_id3]) @@ -98,19 +92,21 @@ class TestPerformance(): "name": "mb", "parent": {"oid": '{}'.format(id)}, "data": { - "path": "C:\\projects\\Test\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\v001\\test_Cylinder_workfileLookdev_v001.mb", - "template": "{root}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" + "path": "C:\\projects\\Test\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\v001\\test_Cylinder_workfileLookdev_v001.mb", + "template": "{root}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" }, "type": "representation", "schema": "pype:representation-2.0" - } + } insert_recs.append(document) - print('Prepared {} records in {} collection'.format(no_of_records, self.collection_name)) - id = self.collection.insert_many(insert_recs) + print('Prepared {} records in {} collection'. + format(no_of_records, self.collection_name)) + + self.collection.insert_many(insert_recs) # TODO refactore to produce real array and not needeing ugly regex - self.collection.insert_one({"inserted_id" : self.inserted_ids}) + self.collection.insert_one({"inserted_id": self.inserted_ids}) print('-' * 50) def run(self, queries=1000, loops=3): @@ -120,9 +116,11 @@ class TestPerformance(): :param loops: loop of testing X queries :return: None ''' - print('Testing version {} on {}'.format(self.version, self.collection_name)) + print('Testing version {} on {}'.format(self.version, + self.collection_name)) - inserted_ids = list(self.collection.find({"inserted_id":{"$exists":True}})) + inserted_ids = list(self.collection. + find({"inserted_id": {"$exists": True}})) import re self.ids = re.findall("'[0-9a-z]*'", str(inserted_ids)) @@ -131,15 +129,16 @@ class TestPerformance(): found_cnt = 0 for _ in range(loops): start = time.time() - for i in range(queries): + for _ in range(queries): val = random.choice(self.ids) - val = val.replace("'",'') - #print(val) + val = val.replace("'", '') + if (self.version == 'array'): # prepared for partial index, without 'files': exists # wont engage - found = self.collection.find_one({'files': {"$exists": True}, - 'files._id': "{}".format(val)}) + found = self.collection.\ + find_one({'files': {"$exists": True}, + 'files._id': "{}".format(val)}) else: key = "files.{}".format(val) found = self.collection.find_one({key: {"$exists": True}}) @@ -167,66 +166,71 @@ class TestPerformance(): def get_files_array(self, i, file_id, file_id2, file_id3): return [ - { - "path":"c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderA_workfileLookdev_v{0:03}.mb".format(i), - "_id": '{}'.format(file_id), - "hash":"temphash", - "sites":["studio"], - "size":87236 - }, - { - "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderB_workfileLookdev_v{0:03}.mb".format( - i), - "_id": '{}'.format(file_id2), - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - }, - { - "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderC_workfileLookdev_v{0:03}.mb".format( - i), - "_id": '{}'.format(file_id3), - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - } - - ] + { + "path": "c:/Test/Assets/Cylinder/publish/workfile/" + "workfileLookdev/v001/" + "test_CylinderA_workfileLookdev_v{0:03}.mb".format(i), + "_id": '{}'.format(file_id), + "hash": "temphash", + "sites": ["studio"], + "size":87236 + }, + { + "path": "c:/Test/Assets/Cylinder/publish/workfile/" + "workfileLookdev/v001/" + "test_CylinderB_workfileLookdev_v{0:03}.mb".format(i), + "_id": '{}'.format(file_id2), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + }, + { + "path": "c:/Test/Assets/Cylinder/publish/workfile/" + "workfileLookdev/v001/" + "test_CylinderC_workfileLookdev_v{0:03}.mb".format(i), + "_id": '{}'.format(file_id3), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } + ] def get_files_doc(self, i, file_id, file_id2, file_id3): ret = {} ret['{}'.format(file_id)] = { - "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderA_workfileLookdev_v{0:03}.mb".format( - i), + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/" + "v001/test_CylinderA_workfileLookdev_v{0:03}.mb".format(i), "hash": "temphash", "sites": ["studio"], "size": 87236 } - ret['{}'.format(file_id2)] = { - "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderB_workfileLookdev_v{0:03}.mb".format(i), - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - } + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/" + "v001/test_CylinderB_workfileLookdev_v{0:03}.mb".format(i), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } ret['{}'.format(file_id3)] = { - "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/v001/test_CylinderC_workfileLookdev_v{0:03}.mb".format(i), - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - } + "path": "c:/Test/Assets/Cylinder/publish/workfile/workfileLookdev/" + "v001/test_CylinderC_workfileLookdev_v{0:03}.mb".format(i), + "hash": "temphash", + "sites": ["studio"], + "size": 87236 + } return ret + if __name__ == '__main__': tp = TestPerformance('array') - tp.prepare() # enable to prepare data + tp.prepare() # enable to prepare data tp.run(1000, 3) print('-'*50) tp = TestPerformance('doc') - tp.prepare() # enable to prepare data + tp.prepare() # enable to prepare data tp.run(1000, 3) From 42c77d0e13aead8cd0d1d27285f60ed07909b377 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 18 Jun 2020 12:04:30 +0200 Subject: [PATCH 014/947] Fix - change level of logging on proper place --- pype/plugins/global/publish/integrate_new.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index c5ef2c3530..bb58479d64 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -600,8 +600,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): except OSError as e: if e.errno == errno.EEXIST: pass - else: - self.log.critical("An unexpected error occurred.") + else:# clean destination + self.log.error("An unexpected error occurred.") raise # copy file with speedcopy and check if size of files are simetrical From fe9ed4c09e6cd04378a1b3f2887bf5a0e011c7a0 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 18 Jun 2020 12:16:14 +0200 Subject: [PATCH 015/947] Fix - unwanted change of existing logging --- pype/plugins/global/publish/integrate_new.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index bb58479d64..c5ef2c3530 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -600,8 +600,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): except OSError as e: if e.errno == errno.EEXIST: pass - else:# clean destination - self.log.error("An unexpected error occurred.") + else: + self.log.critical("An unexpected error occurred.") raise # copy file with speedcopy and check if size of files are simetrical From ca78a5ca2a28aa0c55163b2d5fcc34e595759443 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 18 Jun 2020 12:40:57 +0200 Subject: [PATCH 016/947] Fix - switched from raise to six.reraise Small formatting changes --- pype/plugins/global/publish/integrate_new.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index c5ef2c3530..5c48770f99 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -115,7 +115,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # clean destination self.log.critical("Error when registering", exc_info=True) self.handle_destination_files(self.integrated_file_sizes, 'remove') - raise + six.reraise(*sys.exc_info()) def register(self, instance): # Required environment variables @@ -509,7 +509,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): format(self.integrated_file_sizes)) # get 'files' info for representation and all attached resources - self.log.debug("Preparing files information ..") + self.log.debug("Preparing files information ...") representation["files"] = self.get_files_info( instance, self.integrated_file_sizes) @@ -573,7 +573,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): hardlinks = instance.data.get("hardlinks", list()) for src, dest in hardlinks: dest = self.get_dest_temp_url(dest) - self.log.debug("Hardlinking file .. {} -> {}".format(src, dest)) + self.log.debug("Hardlinking file ... {} -> {}".format(src, dest)) if not os.path.exists(dest): self.hardlink_file(src, dest) @@ -593,7 +593,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): """ src = os.path.normpath(src) dst = os.path.normpath(dst) - self.log.debug("Copying file .. {} -> {}".format(src, dst)) + self.log.debug("Copying file ... {} -> {}".format(src, dst)) dirname = os.path.dirname(dst) try: os.makedirs(dirname) @@ -602,7 +602,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): pass else: self.log.critical("An unexpected error occurred.") - raise + six.reraise(*sys.exc_info()) # copy file with speedcopy and check if size of files are simetrical while True: @@ -625,7 +625,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): pass else: self.log.critical("An unexpected error occurred.") - raise + six.reraise(*sys.exc_info()) filelink.create(src, dst, filelink.HARDLINK) @@ -638,7 +638,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): }) if subset is None: - self.log.info("Subset '%s' not found, creating.." % subset_name) + self.log.info("Subset '%s' not found, creating ..." % subset_name) self.log.debug("families. %s" % instance.data.get('families')) self.log.debug( "families. %s" % type(instance.data.get('families'))) @@ -932,10 +932,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for file_url, file_size in integrated_file_sizes.items(): try: if mode == 'remove': - self.log.debug("Removing file...{}".format(file_url)) + self.log.debug("Removing file ...{}".format(file_url)) os.remove(file_url) if mode == 'finalize': - self.log.debug("Renaming file...{}".format(file_url)) + self.log.debug("Renaming file ...{}".format(file_url)) import re os.rename(file_url, re.sub('\.{}$'.format(self.TMP_FILE_EXT), @@ -948,4 +948,4 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): except OSError: self.log.error("Cannot {} file {}".format(mode, file_url) , exc_info=True) - raise + six.reraise(*sys.exc_info()) From 48c163a3319429ba63eb068c928fc77509c4f88c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 22 Jun 2020 19:30:17 +0300 Subject: [PATCH 017/947] feat(resolve): updating resolve integration wip --- pype/hosts/resolve/__init__.py | 12 +- pype/hosts/resolve/lib.py | 205 +++++++++++++++++- pype/hosts/resolve/pipeline.py | 20 +- pype/hosts/resolve/plugin.py | 8 +- pype/hosts/resolve/utility_scripts/test.py | 4 +- pype/hosts/resolve/utils.py | 16 +- .../resolve/create/create_shot_clip.py | 27 ++- 7 files changed, 255 insertions(+), 37 deletions(-) diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index b7e6c7dee3..5f651f4b29 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -17,6 +17,9 @@ from .lib import ( get_project_manager, get_current_project, get_current_sequence, + get_current_track_items, + create_current_sequence_media_bin, + create_compound_clip, set_project_manager_to_folder_name ) @@ -33,7 +36,8 @@ from .workio import ( work_root ) -bmd = None +bmdvr = None +bmdvf = None __all__ = [ # pipeline @@ -54,6 +58,9 @@ __all__ = [ "get_project_manager", "get_current_project", "get_current_sequence", + "get_current_track_items", + "create_current_sequence_media_bin", + "create_compound_clip", "set_project_manager_to_folder_name", # menu @@ -71,5 +78,6 @@ __all__ = [ "work_root", # singleton with black magic resolve module - "bmd" + "bmdvr", + "bmdvf" ] diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 25e177eb1c..9232f8ec68 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -8,9 +8,9 @@ self.pm = None def get_project_manager(): - from . import bmd + from . import bmdvr if not self.pm: - self.pm = bmd.GetProjectManager() + self.pm = bmdvr.GetProjectManager() return self.pm @@ -28,6 +28,207 @@ def get_current_sequence(): return project.GetCurrentTimeline() +def get_current_track_items( + filter=False, + track_type=None, + selecting_color=None): + """ Gets all available current timeline track items + """ + from pprint import pformat + track_type = track_type or "video" + selecting_color = selecting_color or "Chocolate" + project = get_current_project() + sequence = get_current_sequence() + selected_clips = list() + + # get all tracks count filtered by track type + sequence_video_count = sequence.GetTrackCount(track_type) + + # loop all tracks and get items + _clips = dict() + for track_index in range(1, (int(sequence_video_count) + 1)): + track_name = sequence.GetTrackName(track_type, track_index) + track_track_items = sequence.GetItemListInTrack( + track_type, track_index) + _clips[track_index] = track_track_items + + _data = { + "project": project, + "sequence": sequence, + "track": { + "name": track_name, + "index": track_index, + "type": track_type} + } + # get track item object and its color + for clip_index, ti in enumerate(_clips[track_index]): + data = _data.copy() + data["clip"] = { + "item": ti, + "index": clip_index + } + ti_color = ti.GetClipColor() + if filter is True: + if selecting_color in ti_color: + selected_clips.append(data) + ti.ClearClipColor() + else: + selected_clips.append(data) + + return selected_clips + + +def create_current_sequence_media_bin(sequence): + seq_name = sequence.GetName() + media_pool = get_current_project().GetMediaPool() + root_folder = media_pool.GetRootFolder() + sub_folders = root_folder.GetSubFolderList() + testing_names = list() + + print(f"_ sub_folders: {sub_folders}") + for subfolder in sub_folders: + subf_name = subfolder.GetName() + if seq_name in subf_name: + testing_names.append(subfolder) + else: + testing_names.append(False) + + matching = next((f for f in testing_names if f is not False), None) + + if not matching: + new_folder = media_pool.AddSubFolder(root_folder, seq_name) + media_pool.SetCurrentFolder(new_folder) + else: + media_pool.SetCurrentFolder(matching) + + return media_pool.GetCurrentFolder() + + +def create_compound_clip(clip_data, folder, presets): + """ + Convert timeline object into nested timeline object + + Args: + clip_data (dict): timeline item object packed into dict + with project, timeline (sequence) + folder (resolve.MediaPool.Folder): media pool folder object, + presets (dict): pype config plugin presets + + Returns: + resolve.MediaPoolItem: media pool item with compound clip timeline(cct) + """ + from pprint import pformat + + # get basic objects form data + project = clip_data["project"] + sequence = clip_data["sequence"] + clip = clip_data["clip"] + + # get details of objects + clip_item = clip["item"] + track = clip_data["track"] + + # build name + clip_name_split = clip_item.GetName().split(".") + name = "_".join([ + track["name"], + str(track["index"]), + clip_name_split[0], + str(clip["index"])] + ) + + # get metadata + mp_item = clip_item.GetMediaPoolItem() + mp_props = mp_item.GetClipProperty() + metadata = get_metadata_from_clip(clip_item) + mp = project.GetMediaPool() + + # keep original sequence + sq_origin = sequence + + # print(f"_ sequence: {sequence}") + # print(f"_ metadata: {pformat(metadata)}") + + # Set current folder to input media_pool_folder: + mp.SetCurrentFolder(folder) + + # check if clip doesnt exist already: + clips = folder.GetClipList() + cct = next((c for c in clips + if c.GetName() in name), None) + + if cct: + print(f"_ cct exists: {cct}") + return cct + + # Create empty timeline in current folder and give name: + cct = mp.CreateEmptyTimeline(name) + print(f"_ cct: {cct}") + + # Set current timeline to created timeline: + project.SetCurrentTimeline(cct) + + # Add input clip to the current timeline: + # TODO: set offsets if handles + done = mp.AppendToTimeline([{ + "mediaPoolItem": mp_item, + "startFrame": int(mp_props["Start"]), + "endFrame": int(mp_props["End"]) + }]) + print(f"_ done1: {done}") + + # Set current timeline to the working timeline: + project.SetCurrentTimeline(sq_origin) + + # Add collected metadata to the comound clip: + done = mp_item.SetClipProperty("pypeMetadata", metadata) + print(f"_ done2: {done}") + + return cct + + +def validate_tc(x): + # Validate and reformat timecode string + + if len(x) != 11: + print('Invalid timecode. Try again.') + + c = ':' + colonized = x[:2] + c + x[3:5] + c + x[6:8] + c + x[9:] + + if colonized.replace(':', '').isdigit(): + print(f"_ colonized: {colonized}") + return colonized + else: + print('Invalid timecode. Try again.') + + +def get_metadata_from_clip(clip): + """ + Collect all metadata from resolve timeline item + + Args: + clip (resolve.TimelineItem): timeline item object + + Returns: + dict: all collected metadata as key: values + """ + mp_item = clip.GetMediaPoolItem() + + data = { + "clipIn": clip.GetStart(), + "clipOut": clip.GetEnd(), + "clipLeftOffset": clip.GetLeftOffset(), + "clipRightOffset": clip.GetRightOffset(), + "clipMarkers": clip.GetMarkers(), + "clipFlags": clip.GetFlagList(), + "sourceMetadata": mp_item.GetMetadata(), + "sourceId": mp_item.GetMediaId(), + "sourceProperties": mp_item.GetClipProperty() + } + return data + + def set_project_manager_to_folder_name(folder_name): """ Sets context of Project manager to given folder by name. diff --git a/pype/hosts/resolve/pipeline.py b/pype/hosts/resolve/pipeline.py index 91d06da274..92bef2e13b 100644 --- a/pype/hosts/resolve/pipeline.py +++ b/pype/hosts/resolve/pipeline.py @@ -152,29 +152,11 @@ def maintained_selection(): >>> print(node['selected'].value()) False """ - from . import get_current_project - project = get_current_project() - nodes = [] - previous_selection = None - - # deselect all nodes - reset_selection() - try: # do the operation yield finally: - # unselect all selection in case there is some - reset_selection() - # and select all previously selected nodes - if previous_selection: - try: - for n in nodes: - if n not in previous_selection: - continue - n['selected'].setValue(True) - except ValueError as e: - log.warning(e) + pass def reset_selection(): diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 513b9984f4..002d12106d 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -89,6 +89,10 @@ class Creator(api.Creator): # adding basic current context resolve objects self.project = resolve.get_current_project() self.sequence = resolve.get_current_sequence() - - # TODO: make sure no duplicity of subsets are in workfile + + if (self.options or {}).get("useSelection"): + self.selected = resolve.get_current_track_items(filter=True) + else: + self.selected = resolve.get_current_track_items(filter=False) + return diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py index 4c43507e62..cf7db3b7e5 100644 --- a/pype/hosts/resolve/utility_scripts/test.py +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -10,8 +10,8 @@ def main(): bm = bmdvr.utils.get_resolve_module() log.info(f"blackmagicmodule: {bm}") -import DaVinciResolveScript as bmd -print(f"_>> bmd.scriptapp(Resolve): {bmd.scriptapp('Resolve')}") +import DaVinciResolveScript as bmdvr +print(f"_>> bmdvr.scriptapp(Resolve): {bmdvr.scriptapp('Resolve')}") if __name__ == "__main__": diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/utils.py index 74ce2dc98f..dcc92c5b8d 100644 --- a/pype/hosts/resolve/utils.py +++ b/pype/hosts/resolve/utils.py @@ -15,10 +15,10 @@ log = Logger().get_logger(__name__, "resolve") def get_resolve_module(): from pype.hosts import resolve # dont run if already loaded - if resolve.bmd: + if resolve.bmdvr: log.info(("resolve module is assigned to " - f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) - return resolve.bmd + f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) + return resolve.bmdvr try: """ The PYTHONPATH needs to be set correctly for this import @@ -69,10 +69,14 @@ def get_resolve_module(): ) sys.exit() # assign global var and return - bmd = bmd.scriptapp("Resolve") - resolve.bmd = bmd + bmdvr = bmd.scriptapp("Resolve") + bmdvf = bmd.scriptapp("Fusion") + resolve.bmdvr = bmdvr + resolve.bmdvf = bmdvf log.info(("Assigning resolve module to " - f"`pype.hosts.resolve.bmd`: {resolve.bmd}")) + f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) + log.info(("Assigning resolve module to " + f"`pype.hosts.resolve.bmdvf`: {resolve.bmdvf}")) def _sync_utility_scripts(env=None): diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 43a8ab0cbd..c48ca3a5a6 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,6 +1,6 @@ +from pprint import pformat from pype.hosts import resolve - class CreateShotClip(resolve.Creator): """Publishable clip""" @@ -12,6 +12,25 @@ class CreateShotClip(resolve.Creator): presets = None def process(self): - print(f"Project name: {self.project.GetName()}") - print(f"Sequence name: {self.sequence.GetName()}") - print(self.presets) + project = self.project + sequence = self.sequence + presets = self.presets + print(f"__ selected_clips: {self.selected}") + + # sequence attrs + sq_frame_start = self.sequence.GetStartFrame() + sq_markers = self.sequence.GetMarkers() + print(f"__ sq_frame_start: {pformat(sq_frame_start)}") + print(f"__ seq_markers: {pformat(sq_markers)}") + + # create media bin for compound clips (trackItems) + mp_folder = resolve.create_current_sequence_media_bin(self.sequence) + print(f"_ mp_folder: {mp_folder.GetName()}") + + for t_data in self.selected: + print(t_data) + # convert track item to timeline media pool item + c_clip = resolve.create_compound_clip( + t_data, mp_folder, presets) + + # replace orig clip with compound_clip From 8e91764c4c06aff57d158e853e3e51d191dcd3dd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Jun 2020 12:47:06 +0300 Subject: [PATCH 018/947] feat(resolve): create compound clip wip --- pype/hosts/resolve/lib.py | 34 ++++++++++++++++++++++++---------- pype/hosts/resolve/utils.py | 4 ++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 9232f8ec68..5a5980a462 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,4 +1,5 @@ import sys +import json from pype.api import Logger log = Logger().get_logger(__name__, "resolve") @@ -140,14 +141,14 @@ def create_compound_clip(clip_data, folder, presets): # get metadata mp_item = clip_item.GetMediaPoolItem() mp_props = mp_item.GetClipProperty() - metadata = get_metadata_from_clip(clip_item) + clip_attributes = get_clip_attributes(clip_item) mp = project.GetMediaPool() # keep original sequence sq_origin = sequence - # print(f"_ sequence: {sequence}") - # print(f"_ metadata: {pformat(metadata)}") + print(f"_ sequence: {sequence}") + print(f"_ metadata: {pformat(clip_attributes)}") # Set current folder to input media_pool_folder: mp.SetCurrentFolder(folder) @@ -163,7 +164,12 @@ def create_compound_clip(clip_data, folder, presets): # Create empty timeline in current folder and give name: cct = mp.CreateEmptyTimeline(name) - print(f"_ cct: {cct}") + + # check if clip doesnt exist already: + clips = folder.GetClipList() + cct = next((c for c in clips + if c.GetName() in name), None) + print(f"_ cct created: {cct}") # Set current timeline to created timeline: project.SetCurrentTimeline(cct) @@ -180,10 +186,19 @@ def create_compound_clip(clip_data, folder, presets): # Set current timeline to the working timeline: project.SetCurrentTimeline(sq_origin) - # Add collected metadata to the comound clip: - done = mp_item.SetClipProperty("pypeMetadata", metadata) + # Add collected metadata and attributes to the comound clip: + clip_attributes["VFX Notes"] = mp_item.GetMetadata( + "VFX Notes")["VFX Notes"] + clip_attributes = json.dumps(clip_attributes) + + for k, v in mp_item.GetMetadata().items(): + done = cct.SetMetadata(k, v) + + done = cct.SetMetadata("VFX Notes", clip_attributes) print(f"_ done2: {done}") + # # add clip item as take to timeline + # AddTake(cct, startFrame, endFrame) return cct @@ -203,15 +218,15 @@ def validate_tc(x): print('Invalid timecode. Try again.') -def get_metadata_from_clip(clip): +def get_clip_attributes(clip): """ - Collect all metadata from resolve timeline item + Collect basic atrributes from resolve timeline item Args: clip (resolve.TimelineItem): timeline item object Returns: - dict: all collected metadata as key: values + dict: all collected attributres as key: values """ mp_item = clip.GetMediaPoolItem() @@ -222,7 +237,6 @@ def get_metadata_from_clip(clip): "clipRightOffset": clip.GetRightOffset(), "clipMarkers": clip.GetMarkers(), "clipFlags": clip.GetFlagList(), - "sourceMetadata": mp_item.GetMetadata(), "sourceId": mp_item.GetMediaId(), "sourceProperties": mp_item.GetClipProperty() } diff --git a/pype/hosts/resolve/utils.py b/pype/hosts/resolve/utils.py index dcc92c5b8d..e11cc64b3b 100644 --- a/pype/hosts/resolve/utils.py +++ b/pype/hosts/resolve/utils.py @@ -70,9 +70,9 @@ def get_resolve_module(): sys.exit() # assign global var and return bmdvr = bmd.scriptapp("Resolve") - bmdvf = bmd.scriptapp("Fusion") + # bmdvf = bmd.scriptapp("Fusion") resolve.bmdvr = bmdvr - resolve.bmdvf = bmdvf + resolve.bmdvf = bmdvr.Fusion() log.info(("Assigning resolve module to " f"`pype.hosts.resolve.bmdvr`: {resolve.bmdvr}")) log.info(("Assigning resolve module to " From 34c27c3fbf7aac183017540f73e15e8335416c2d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Jun 2020 21:02:41 +0300 Subject: [PATCH 019/947] feat(resolve): compound clip create with sequencial rename --- pype/hosts/resolve/lib.py | 228 ++++++++++++++---- .../resolve/create/create_shot_clip.py | 16 +- 2 files changed, 192 insertions(+), 52 deletions(-) diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 5a5980a462..50b70241c0 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,11 +1,15 @@ import sys import json +from opentimelineio import opentime + from pype.api import Logger log = Logger().get_logger(__name__, "resolve") self = sys.modules[__name__] self.pm = None +self.rename_index = 0 +self.rename_add = 0 def get_project_manager(): @@ -35,7 +39,6 @@ def get_current_track_items( selecting_color=None): """ Gets all available current timeline track items """ - from pprint import pformat track_type = track_type or "video" selecting_color = selecting_color or "Chocolate" project = get_current_project() @@ -105,7 +108,75 @@ def create_current_sequence_media_bin(sequence): return media_pool.GetCurrentFolder() -def create_compound_clip(clip_data, folder, presets): +def get_name_with_data(clip_data, presets): + """ + Take hierarchy data from presets and build name with parents data + + Args: + clip_data (dict): clip data from `get_current_track_items()` + presets (dict): data from create plugin + + Returns: + list: name, data + + """ + def _replace_hash_to_expression(name, text): + _spl = text.split("#") + _len = (len(_spl) - 1) + _repl = f"{{{name}:0>{_len}}}" + new_text = text.replace(("#" * _len), _repl) + return new_text + + # presets data + clip_name = presets["clipName"] + hierarchy = presets["hierarchy"] + hierarchy_data = presets["hierarchyData"].copy() + count_from = presets["countFrom"] + steps = presets["steps"] + + # reset rename_add + if self.rename_add < count_from: + self.rename_add = count_from + + # shot num calculate + if self.rename_index == 0: + shot_num = self.rename_add + else: + shot_num = self.rename_add + steps + + print(f"shot_num: {shot_num}") + + # clip data + _data = { + "sequence": clip_data["sequence"].GetName(), + "track": clip_data["track"]["name"].replace(" ", "_"), + "shot": shot_num + } + + # solve # in test to pythonic explression + for k, v in hierarchy_data.items(): + if "#" not in v: + continue + hierarchy_data[k] = _replace_hash_to_expression(k, v) + + # fill up pythonic expresisons + for k, v in hierarchy_data.items(): + hierarchy_data[k] = v.format(**_data) + + # fill up clip name and hierarchy keys + hierarchy = hierarchy.format(**hierarchy_data) + clip_name = clip_name.format(**hierarchy_data) + + self.rename_add = shot_num + print(f"shot_num: {shot_num}") + + return (clip_name, { + "hierarchy": hierarchy, + "hierarchyData": hierarchy_data + }) + + +def create_compound_clip(clip_data, folder, rename=False, **kwargs): """ Convert timeline object into nested timeline object @@ -113,12 +184,12 @@ def create_compound_clip(clip_data, folder, presets): clip_data (dict): timeline item object packed into dict with project, timeline (sequence) folder (resolve.MediaPool.Folder): media pool folder object, - presets (dict): pype config plugin presets + rename (bool)[optional]: renaming in sequence or not + kwargs (optional): additional data needed for rename=True (presets) Returns: resolve.MediaPoolItem: media pool item with compound clip timeline(cct) """ - from pprint import pformat # get basic objects form data project = clip_data["project"] @@ -129,27 +200,53 @@ def create_compound_clip(clip_data, folder, presets): clip_item = clip["item"] track = clip_data["track"] - # build name - clip_name_split = clip_item.GetName().split(".") - name = "_".join([ - track["name"], - str(track["index"]), - clip_name_split[0], - str(clip["index"])] - ) + mp = project.GetMediaPool() + + # get clip attributes + clip_attributes = get_clip_attributes(clip_item) + + if rename: + presets = kwargs.get("presets") + if presets: + name, data = get_name_with_data(clip_data, presets) + # add hirarchy data to clip attributes + clip_attributes.update(data) + else: + name = "{:0>3}_{:0>4}".format( + int(track["index"]), int(clip["index"])) + else: + # build name + clip_name_split = clip_item.GetName().split(".") + name = "_".join([ + track["name"], + str(track["index"]), + clip_name_split[0], + str(clip["index"])] + ) # get metadata mp_item = clip_item.GetMediaPoolItem() mp_props = mp_item.GetClipProperty() - clip_attributes = get_clip_attributes(clip_item) - mp = project.GetMediaPool() + + mp_first_frame = int(mp_props["Start"]) + mp_last_frame = int(mp_props["End"]) + + # initialize basic source timing for otio + ci_l_offset = clip_item.GetLeftOffset() + ci_duration = clip_item.GetDuration() + rate = float(mp_props["FPS"]) + + # source rational times + mp_in_rc = opentime.RationalTime((ci_l_offset), rate) + mp_out_rc = opentime.RationalTime((ci_l_offset + ci_duration - 1), rate) + + # get frame in and out for clip swaping + in_frame = opentime.to_frames(mp_in_rc) + out_frame = opentime.to_frames(mp_out_rc) # keep original sequence sq_origin = sequence - print(f"_ sequence: {sequence}") - print(f"_ metadata: {pformat(clip_attributes)}") - # Set current folder to input media_pool_folder: mp.SetCurrentFolder(folder) @@ -160,48 +257,89 @@ def create_compound_clip(clip_data, folder, presets): if cct: print(f"_ cct exists: {cct}") - return cct + else: + # Create empty timeline in current folder and give name: + cct = mp.CreateEmptyTimeline(name) - # Create empty timeline in current folder and give name: - cct = mp.CreateEmptyTimeline(name) + # check if clip doesnt exist already: + clips = folder.GetClipList() + cct = next((c for c in clips + if c.GetName() in name), None) + print(f"_ cct created: {cct}") - # check if clip doesnt exist already: - clips = folder.GetClipList() - cct = next((c for c in clips - if c.GetName() in name), None) - print(f"_ cct created: {cct}") + # Set current timeline to created timeline: + project.SetCurrentTimeline(cct) - # Set current timeline to created timeline: - project.SetCurrentTimeline(cct) + # Add input clip to the current timeline: + mp.AppendToTimeline([{ + "mediaPoolItem": mp_item, + "startFrame": mp_first_frame, + "endFrame": mp_last_frame + }]) - # Add input clip to the current timeline: - # TODO: set offsets if handles - done = mp.AppendToTimeline([{ - "mediaPoolItem": mp_item, - "startFrame": int(mp_props["Start"]), - "endFrame": int(mp_props["End"]) - }]) - print(f"_ done1: {done}") - - # Set current timeline to the working timeline: - project.SetCurrentTimeline(sq_origin) + # Set current timeline to the working timeline: + project.SetCurrentTimeline(sq_origin) # Add collected metadata and attributes to the comound clip: - clip_attributes["VFX Notes"] = mp_item.GetMetadata( - "VFX Notes")["VFX Notes"] + if clip_attributes.get("VFX Notes"): + clip_attributes["VFX Notes"] = mp_item.GetMetadata( + "VFX Notes")["VFX Notes"] clip_attributes = json.dumps(clip_attributes) + # add attributes to metadata for k, v in mp_item.GetMetadata().items(): - done = cct.SetMetadata(k, v) + cct.SetMetadata(k, v) - done = cct.SetMetadata("VFX Notes", clip_attributes) - print(f"_ done2: {done}") + # add metadata to cct + cct.SetMetadata("VFX Notes", clip_attributes) - # # add clip item as take to timeline - # AddTake(cct, startFrame, endFrame) + # reset start timecode of the compound clip + cct.SetClipProperty("Start TC", mp_props["Start TC"]) + + # swap clips on timeline + swap_clips(clip_item, cct, name, in_frame, out_frame) + + cct.SetClipColor("Pink") return cct +def swap_clips(from_clip, to_clip, to_clip_name, to_in_frame, to_out_frame): + """ + Swaping clips on timeline in timelineItem + + It will add take and activate it to the frame range which is inputted + + Args: + from_clip (resolve.mediaPoolItem) + to_clip (resolve.mediaPoolItem) + to_clip_name (str): name of to_clip + to_in_frame (float): cut in frame, usually `GetLeftOffset()` + to_out_frame (float): cut out frame, usually left offset plus duration + + Returns: + bool: True if successfully replaced + + """ + # add clip item as take to timeline + take = from_clip.AddTake( + to_clip, + float(to_in_frame), + float(to_out_frame) + ) + + if not take: + return False + + for take_index in range(1, (int(from_clip.GetTakesCount()) + 1)): + take_item = from_clip.GetTakeByIndex(take_index) + take_mp_item = take_item["mediaPoolItem"] + if to_clip_name in take_mp_item.GetName(): + from_clip.SelectTakeByIndex(take_index) + from_clip.FinalizeTake() + return True + return False + + def validate_tc(x): # Validate and reformat timecode string diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index c48ca3a5a6..c12d5541d4 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,5 +1,7 @@ from pprint import pformat from pype.hosts import resolve +from avalon.vendor import Qt + class CreateShotClip(resolve.Creator): """Publishable clip""" @@ -12,9 +14,8 @@ class CreateShotClip(resolve.Creator): presets = None def process(self): - project = self.project - sequence = self.sequence - presets = self.presets + from pype.hosts.resolve import lib + print(f"__ selected_clips: {self.selected}") # sequence attrs @@ -27,10 +28,11 @@ class CreateShotClip(resolve.Creator): mp_folder = resolve.create_current_sequence_media_bin(self.sequence) print(f"_ mp_folder: {mp_folder.GetName()}") - for t_data in self.selected: + lib.rename_add = 0 + for i, t_data in enumerate(self.selected): + lib.rename_index = i print(t_data) # convert track item to timeline media pool item c_clip = resolve.create_compound_clip( - t_data, mp_folder, presets) - - # replace orig clip with compound_clip + t_data, mp_folder, rename=True, **dict( + {"presets": self.presets})) From 4f3565d2cf85d5ff4171f5247176603ce56642b2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Jun 2020 12:55:17 +0300 Subject: [PATCH 020/947] feat(resolve): basic publish collecting of clips --- pype/hosts/resolve/__init__.py | 4 + pype/hosts/resolve/lib.py | 34 +++- pype/plugins/resolve/publish/collect_clips.py | 159 ++++++++++++++++++ 3 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 pype/plugins/resolve/publish/collect_clips.py diff --git a/pype/hosts/resolve/__init__.py b/pype/hosts/resolve/__init__.py index 5f651f4b29..c8f45259ff 100644 --- a/pype/hosts/resolve/__init__.py +++ b/pype/hosts/resolve/__init__.py @@ -20,6 +20,8 @@ from .lib import ( get_current_track_items, create_current_sequence_media_bin, create_compound_clip, + swap_clips, + get_pype_clip_metadata, set_project_manager_to_folder_name ) @@ -61,6 +63,8 @@ __all__ = [ "get_current_track_items", "create_current_sequence_media_bin", "create_compound_clip", + "swap_clips", + "get_pype_clip_metadata", "set_project_manager_to_folder_name", # menu diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index 50b70241c0..db3bd989bf 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -1,6 +1,7 @@ import sys import json from opentimelineio import opentime +from pprint import pformat from pype.api import Logger @@ -10,6 +11,7 @@ self = sys.modules[__name__] self.pm = None self.rename_index = 0 self.rename_add = 0 +self.pype_metadata_key = "VFX Notes" def get_project_manager(): @@ -46,11 +48,11 @@ def get_current_track_items( selected_clips = list() # get all tracks count filtered by track type - sequence_video_count = sequence.GetTrackCount(track_type) + selected_track_count = sequence.GetTrackCount(track_type) # loop all tracks and get items _clips = dict() - for track_index in range(1, (int(sequence_video_count) + 1)): + for track_index in range(1, (int(selected_track_count) + 1)): track_name = sequence.GetTrackName(track_type, track_index) track_track_items = sequence.GetItemListInTrack( track_type, track_index) @@ -190,7 +192,6 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): Returns: resolve.MediaPoolItem: media pool item with compound clip timeline(cct) """ - # get basic objects form data project = clip_data["project"] sequence = clip_data["sequence"] @@ -204,6 +205,7 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): # get clip attributes clip_attributes = get_clip_attributes(clip_item) + print(f"_ clip_attributes: {pformat(clip_attributes)}") if rename: presets = kwargs.get("presets") @@ -281,9 +283,11 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): project.SetCurrentTimeline(sq_origin) # Add collected metadata and attributes to the comound clip: - if clip_attributes.get("VFX Notes"): - clip_attributes["VFX Notes"] = mp_item.GetMetadata( - "VFX Notes")["VFX Notes"] + if mp_item.GetMetadata(self.pype_metadata_key): + clip_attributes[self.pype_metadata_key] = mp_item.GetMetadata( + self.pype_metadata_key)[self.pype_metadata_key] + + # stringify clip_attributes = json.dumps(clip_attributes) # add attributes to metadata @@ -291,7 +295,7 @@ def create_compound_clip(clip_data, folder, rename=False, **kwargs): cct.SetMetadata(k, v) # add metadata to cct - cct.SetMetadata("VFX Notes", clip_attributes) + cct.SetMetadata(self.pype_metadata_key, clip_attributes) # reset start timecode of the compound clip cct.SetClipProperty("Start TC", mp_props["Start TC"]) @@ -356,6 +360,22 @@ def validate_tc(x): print('Invalid timecode. Try again.') +def get_pype_clip_metadata(clip): + """ + Get pype metadata created by creator plugin + + Attributes: + clip (resolve.TimelineItem): resolve's object + + Returns: + dict: hierarchy, orig clip attributes + """ + mp_item = clip.GetMediaPoolItem() + metadata = mp_item.GetMetadata() + + return metadata.get(self.pype_metadata_key) + + def get_clip_attributes(clip): """ Collect basic atrributes from resolve timeline item diff --git a/pype/plugins/resolve/publish/collect_clips.py b/pype/plugins/resolve/publish/collect_clips.py new file mode 100644 index 0000000000..0f02f26f2e --- /dev/null +++ b/pype/plugins/resolve/publish/collect_clips.py @@ -0,0 +1,159 @@ +import os +from pyblish import api +from pype.hosts import resolve +import json + +class CollectClips(api.ContextPlugin): + """Collect all Track items selection.""" + + order = api.CollectorOrder + 0.01 + label = "Collect Clips" + hosts = ["resolve"] + + def process(self, context): + # create asset_names conversion table + if not context.data.get("assetsShared"): + self.log.debug("Created `assetsShared` in context") + context.data["assetsShared"] = dict() + + projectdata = context.data["projectEntity"]["data"] + selection = resolve.get_current_track_items( + filter=True, selecting_color="Pink") + + for clip_data in selection: + data = dict() + + # get basic objects form data + project = clip_data["project"] + sequence = clip_data["sequence"] + clip = clip_data["clip"] + + # sequence attrs + sq_frame_start = sequence.GetStartFrame() + self.log.debug(f"sq_frame_start: {sq_frame_start}") + + sq_markers = sequence.GetMarkers() + + # get details of objects + clip_item = clip["item"] + track = clip_data["track"] + + mp = project.GetMediaPool() + + # get clip attributes + clip_metadata = resolve.get_pype_clip_metadata(clip_item) + clip_metadata = json.loads(clip_metadata) + self.log.debug(f"clip_metadata: {clip_metadata}") + + compound_source_prop = clip_metadata["sourceProperties"] + self.log.debug(f"compound_source_prop: {compound_source_prop}") + + asset_name = clip_item.GetName() + mp_item = clip_item.GetMediaPoolItem() + mp_prop = mp_item.GetClipProperty() + source_first = int(compound_source_prop["Start"]) + source_last = int(compound_source_prop["End"]) + source_duration = compound_source_prop["Frames"] + fps = float(mp_prop["FPS"]) + self.log.debug(f"source_first: {source_first}") + self.log.debug(f"source_last: {source_last}") + self.log.debug(f"source_duration: {source_duration}") + self.log.debug(f"fps: {fps}") + + source_path = os.path.normpath( + compound_source_prop["File Path"]) + source_name = compound_source_prop["File Name"] + source_id = clip_metadata["sourceId"] + self.log.debug(f"source_path: {source_path}") + self.log.debug(f"source_name: {source_name}") + self.log.debug(f"source_id: {source_id}") + + clip_left_offset = int(clip_item.GetLeftOffset()) + clip_right_offset = int(clip_item.GetRightOffset()) + self.log.debug(f"clip_left_offset: {clip_left_offset}") + self.log.debug(f"clip_right_offset: {clip_right_offset}") + + # source in/out + source_in = int(source_first + clip_left_offset) + source_out = int(source_first + clip_right_offset) + self.log.debug(f"source_in: {source_in}") + self.log.debug(f"source_out: {source_out}") + + clip_in = int(clip_item.GetStart() - sq_frame_start) + clip_out = int(clip_item.GetEnd() - sq_frame_start) + clip_duration = int(clip_item.GetDuration()) + self.log.debug(f"clip_in: {clip_in}") + self.log.debug(f"clip_out: {clip_out}") + self.log.debug(f"clip_duration: {clip_duration}") + + is_sequence = False + + self.log.debug( + "__ assets_shared: {}".format( + context.data["assetsShared"])) + + # Check for clips with the same range + # this is for testing if any vertically neighbouring + # clips has been already processed + clip_matching_with_range = next( + (k for k, v in context.data["assetsShared"].items() + if (v.get("_clipIn", 0) == clip_in) + and (v.get("_clipOut", 0) == clip_out) + ), False) + + # check if clip name is the same in matched + # vertically neighbouring clip + # if it is then it is correct and resent variable to False + # not to be rised wrong name exception + if asset_name in str(clip_matching_with_range): + clip_matching_with_range = False + + # rise wrong name exception if found one + assert (not clip_matching_with_range), ( + "matching clip: {asset}" + " timeline range ({clip_in}:{clip_out})" + " conflicting with {clip_matching_with_range}" + " >> rename any of clips to be the same as the other <<" + ).format( + **locals()) + + if ("[" in source_name) and ("]" in source_name): + is_sequence = True + + data.update({ + "name": "_".join([ + track["name"], asset_name, source_name]), + "item": clip_item, + "source": mp_item, + # "timecodeStart": str(source.timecodeStart()), + "timelineStart": sq_frame_start, + "sourcePath": source_path, + "sourceFileHead": source_name, + "isSequence": is_sequence, + "track": track["name"], + "trackIndex": track["index"], + "sourceFirst": source_first, + + "sourceIn": source_in, + "sourceOut": source_out, + "mediaDuration": source_duration, + "clipIn": clip_in, + "clipOut": clip_out, + "clipDuration": clip_duration, + "asset": asset_name, + "subset": "plateMain", + "family": "clip", + "families": [], + "handleStart": projectdata.get("handleStart", 0), + "handleEnd": projectdata.get("handleEnd", 0)}) + + instance = context.create_instance(**data) + + self.log.info("Created instance: {}".format(instance)) + self.log.info("Created instance.data: {}".format(instance.data)) + + context.data["assetsShared"][asset_name] = { + "_clipIn": clip_in, + "_clipOut": clip_out + } + self.log.info("context.data[\"assetsShared\"]: {}".format(context.data["assetsShared"])) From 7465d8cb7394ec1c3987b16da782a8b84f66cfd2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Jun 2020 20:17:22 +0300 Subject: [PATCH 021/947] feat(resolve): creator with sequencial rename gui --- pype/hosts/resolve/menu_style.qss | 8 ++ pype/hosts/resolve/plugin.py | 76 ++++++++++++++++++- .../resolve/create/create_shot_clip.py | 53 ++++++++++++- 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index df4fd7e949..c43886c7c3 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -20,6 +20,14 @@ QPushButton:hover { color: #e64b3d; } +QSpinBox { + background-color: #ffffff; +} + +QLineEdit { + background-color: #ffffff; +} + #PypeMenu { border: 1px solid #fef9ef; } diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 002d12106d..2eff278b80 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,8 +1,82 @@ +import sys from avalon import api from pype.hosts import resolve from avalon.vendor import qargparse from pype.api import config +from Qt import QtWidgets + + +class Universal_widget(QtWidgets.QDialog): + def __init__(self, widgets, parent=None): + super(Universal_widget, self).__init__(parent) + + # Where inputs and labels are set + content_widget = QtWidgets.QWidget(self) + content_layout = QtWidgets.QFormLayout(content_widget) + + self.items = dict() + for w in widgets: + attr = getattr(QtWidgets, w["type"]) + label = QtWidgets.QLabel(w["label"]) + attr_name = w["label"].replace(" ", "").lower() + setattr( + self, + attr_name, + attr(parent=self)) + item = getattr(self, attr_name) + func = next((k for k in w if k not in ["label", "type"]), None) + if func: + if getattr(item, func): + func_attr = getattr(item, func) + func_attr(w[func]) + + content_layout.addRow(label, item) + self.items.update({ + w["label"]: item + }) + + # Confirmation buttons + btns_widget = QtWidgets.QWidget(self) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + + cancel_btn = QtWidgets.QPushButton("Cancel") + btns_layout.addWidget(cancel_btn) + + ok_btn = QtWidgets.QPushButton("Ok") + btns_layout.addWidget(ok_btn) + + # Main layout of the dialog + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + main_layout.addWidget(content_widget) + main_layout.addWidget(btns_widget) + + ok_btn.clicked.connect(self._on_ok_clicked) + cancel_btn.clicked.connect(self._on_cancel_clicked) + + stylesheet = resolve.menu.load_stylesheet() + self.setStyleSheet(stylesheet) + + def _on_ok_clicked(self): + self.value() + self.close() + + def _on_cancel_clicked(self): + self.result = None + self.close() + + def value(self): + for k, v in self.items.items(): + if getattr(v, "value", None): + result = getattr(v, "value") + else: + result = getattr(v, "text") + self.items[k] = result() + self.result = self.items + def get_reference_node_parents(ref): """Return all parent reference nodes of reference node @@ -95,4 +169,4 @@ class Creator(api.Creator): else: self.selected = resolve.get_current_track_items(filter=False) - return + self.widget = Universal_widget diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index c12d5541d4..bba9851c0f 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,6 +1,13 @@ from pprint import pformat from pype.hosts import resolve -from avalon.vendor import Qt +from pype.hosts.resolve import lib +import re + + +def camel_case_split(text): + matches = re.finditer( + '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) + return " ".join([str(m.group(0)).capitalize() for m in matches]) class CreateShotClip(resolve.Creator): @@ -13,11 +20,51 @@ class CreateShotClip(resolve.Creator): presets = None - def process(self): - from pype.hosts.resolve import lib + # widget + layout = [{ + "type": "QLabel", + "label": "Define sequencial rename" + }] + def add_presets_to_layout(self, data): + for k, v in data.items(): + if isinstance(v, dict): + self.layout.append({ + "type": "QLabel", + "label": camel_case_split(k) + }) + self.add_presets_to_layout(v) + elif isinstance(v, str): + self.layout.append({ + "type": "QLineEdit", + "label": camel_case_split(k), + "setText": v + }) + elif isinstance(v, int): + self.layout.append({ + "type": "QSpinBox", + "label": camel_case_split(k), + "setValue": v + }) + + def process(self): print(f"__ selected_clips: {self.selected}") + if len(self.selected) < 1: + return + + self.add_presets_to_layout(self.presets) + + widget = self.widget(self.layout) + widget.exec_() + + print(widget.result) + if widget.result: + print("success") + return + else: + return + # sequence attrs sq_frame_start = self.sequence.GetStartFrame() sq_markers = self.sequence.GetMarkers() From ccadf98c501bc4ee5ae447b846bcc93bb8a6e156 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 25 Jun 2020 09:17:35 +0300 Subject: [PATCH 022/947] feat(resolve): update creator input widget --- pype/hosts/resolve/menu_style.qss | 6 ++++++ pype/hosts/resolve/plugin.py | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index c43886c7c3..516c9af72b 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -22,10 +22,16 @@ QPushButton:hover { QSpinBox { background-color: #ffffff; + padding: 5; } QLineEdit { background-color: #ffffff; + padding: 5; +} + +QLabel { + color: #ffffff; } #PypeMenu { diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 2eff278b80..c1aa05dc7d 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -4,13 +4,24 @@ from pype.hosts import resolve from avalon.vendor import qargparse from pype.api import config -from Qt import QtWidgets +from Qt import QtWidgets, QtCore class Universal_widget(QtWidgets.QDialog): def __init__(self, widgets, parent=None): super(Universal_widget, self).__init__(parent) + self.setObjectName("PypeCreatorInput") + + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + self.setWindowTitle("CreatorInput") + # Where inputs and labels are set content_widget = QtWidgets.QWidget(self) content_layout = QtWidgets.QFormLayout(content_widget) @@ -48,7 +59,7 @@ class Universal_widget(QtWidgets.QDialog): # Main layout of the dialog main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setContentsMargins(10, 20, 10, 20) main_layout.setSpacing(0) main_layout.addWidget(content_widget) From 3fb4460b7b412bddff727e438d706c1c567a987f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 26 Jun 2020 14:13:54 +0300 Subject: [PATCH 023/947] feat(resolve): creator plugin with dynamic widget --- pype/hosts/resolve/menu_style.qss | 4 + pype/hosts/resolve/plugin.py | 84 ++++++++++++++----- .../resolve/create/create_shot_clip.py | 39 +-------- 3 files changed, 69 insertions(+), 58 deletions(-) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index 516c9af72b..f3bfa7a30c 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -41,3 +41,7 @@ QLabel { #Spacer { background-color: #282828; } + +#ContentLayout { + background-color: #585858; +} diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index c1aa05dc7d..8c5fd321d1 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,4 +1,5 @@ import sys +import re from avalon import api from pype.hosts import resolve from avalon.vendor import qargparse @@ -8,7 +9,11 @@ from Qt import QtWidgets, QtCore class Universal_widget(QtWidgets.QDialog): - def __init__(self, widgets, parent=None): + + # output items + items = dict() + + def __init__(self, name, presets, parent=None): super(Universal_widget, self).__init__(parent) self.setObjectName("PypeCreatorInput") @@ -24,28 +29,14 @@ class Universal_widget(QtWidgets.QDialog): # Where inputs and labels are set content_widget = QtWidgets.QWidget(self) - content_layout = QtWidgets.QFormLayout(content_widget) + self.content_layout = QtWidgets.QFormLayout(content_widget) + self.content_layout.setObjectName("ContentLayout") - self.items = dict() - for w in widgets: - attr = getattr(QtWidgets, w["type"]) - label = QtWidgets.QLabel(w["label"]) - attr_name = w["label"].replace(" ", "").lower() - setattr( - self, - attr_name, - attr(parent=self)) - item = getattr(self, attr_name) - func = next((k for k in w if k not in ["label", "type"]), None) - if func: - if getattr(item, func): - func_attr = getattr(item, func) - func_attr(w[func]) + # first add widget tag line + self.create_row("QLabel", name) - content_layout.addRow(label, item) - self.items.update({ - w["label"]: item - }) + # add preset data into input widget layout + self.add_presets_to_layout(presets) # Confirmation buttons btns_widget = QtWidgets.QWidget(self) @@ -88,6 +79,57 @@ class Universal_widget(QtWidgets.QDialog): self.items[k] = result() self.result = self.items + def camel_case_split(self, text): + matches = re.finditer( + '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) + return " ".join([str(m.group(0)).capitalize() for m in matches]) + + def create_row(self, type, text, **kwargs): + # get type attribute from qwidgets + attr = getattr(QtWidgets, type) + + # convert label text to normal capitalized text with spaces + label_text = self.camel_case_split(text) + + # assign the new text to lable widget + label = QtWidgets.QLabel(label_text) + + # create attribute name text strip of spaces + attr_name = text.replace(" ", "") + + # create attribute and assign default values + setattr( + self, + attr_name, + attr(parent=self)) + + # assign the created attribute to variable + item = getattr(self, attr_name) + for func, val in kwargs.items(): + if getattr(item, func): + func_attr = getattr(item, func) + func_attr(val) + + self.content_layout.addRow(label, item) + return item + + def add_presets_to_layout(self, data): + for k, v in data.items(): + if isinstance(v, dict): + # if nested dict then create label + # TODO: create also new layout + self.create_row("QLabel", k) + self.add_presets_to_layout(v) + elif isinstance(v, str): + item = self.create_row("QLineEdit", k, setText=v) + elif isinstance(v, int): + item = self.create_row("QSpinBox", k, setValue=v) + + # add it to items for later requests + self.items.update({ + k: item + }) + def get_reference_node_parents(ref): """Return all parent reference nodes of reference node diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index bba9851c0f..18759a2f98 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -1,13 +1,6 @@ from pprint import pformat from pype.hosts import resolve from pype.hosts.resolve import lib -import re - - -def camel_case_split(text): - matches = re.finditer( - '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) - return " ".join([str(m.group(0)).capitalize() for m in matches]) class CreateShotClip(resolve.Creator): @@ -18,44 +11,16 @@ class CreateShotClip(resolve.Creator): icon = "film" defaults = ["Main"] + gui_name = "Define sequencial rename" presets = None - # widget - layout = [{ - "type": "QLabel", - "label": "Define sequencial rename" - }] - - def add_presets_to_layout(self, data): - for k, v in data.items(): - if isinstance(v, dict): - self.layout.append({ - "type": "QLabel", - "label": camel_case_split(k) - }) - self.add_presets_to_layout(v) - elif isinstance(v, str): - self.layout.append({ - "type": "QLineEdit", - "label": camel_case_split(k), - "setText": v - }) - elif isinstance(v, int): - self.layout.append({ - "type": "QSpinBox", - "label": camel_case_split(k), - "setValue": v - }) - def process(self): print(f"__ selected_clips: {self.selected}") if len(self.selected) < 1: return - self.add_presets_to_layout(self.presets) - - widget = self.widget(self.layout) + widget = self.widget(self.gui_name, self.presets) widget.exec_() print(widget.result) From 4391a14da2a2f4063b3692a089b2ae9a1c948040 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 26 Jun 2020 20:28:15 +0300 Subject: [PATCH 024/947] feat(resolve): adding Create widget with style --- pype/hosts/resolve/lib.py | 2 +- pype/hosts/resolve/menu_style.qss | 29 +++-- pype/hosts/resolve/plugin.py | 112 +++++++++++++----- .../resolve/create/create_shot_clip.py | 25 ++-- 4 files changed, 116 insertions(+), 52 deletions(-) diff --git a/pype/hosts/resolve/lib.py b/pype/hosts/resolve/lib.py index db3bd989bf..deb4fa6339 100644 --- a/pype/hosts/resolve/lib.py +++ b/pype/hosts/resolve/lib.py @@ -77,7 +77,7 @@ def get_current_track_items( if filter is True: if selecting_color in ti_color: selected_clips.append(data) - ti.ClearClipColor() + # ti.ClearClipColor() else: selected_clips.append(data) diff --git a/pype/hosts/resolve/menu_style.qss b/pype/hosts/resolve/menu_style.qss index f3bfa7a30c..ea11c4ca2e 100644 --- a/pype/hosts/resolve/menu_style.qss +++ b/pype/hosts/resolve/menu_style.qss @@ -1,6 +1,7 @@ QWidget { background-color: #282828; border-radius: 3; + font-size: 13px; } QPushButton { @@ -21,27 +22,37 @@ QPushButton:hover { } QSpinBox { - background-color: #ffffff; - padding: 5; + border: 1px solid #090909; + background-color: #201f1f; + color: #ffffff; + padding: 2; + max-width: 8em; + qproperty-alignment: AlignCenter; } QLineEdit { - background-color: #ffffff; - padding: 5; -} - -QLabel { + border: 1px solid #090909; + border-radius: 3px; + background-color: #201f1f; color: #ffffff; + padding: 2; + min-width: 10em; + qproperty-alignment: AlignCenter; } #PypeMenu { border: 1px solid #fef9ef; } -#Spacer { +QVBoxLayout { background-color: #282828; } -#ContentLayout { +#Devider { + border: 1px solid #090909; background-color: #585858; } + +QLabel { + color: #77776b; +} diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 8c5fd321d1..4e7ac80add 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -1,4 +1,3 @@ -import sys import re from avalon import api from pype.hosts import resolve @@ -13,10 +12,10 @@ class Universal_widget(QtWidgets.QDialog): # output items items = dict() - def __init__(self, name, presets, parent=None): + def __init__(self, name, info, presets, parent=None): super(Universal_widget, self).__init__(parent) - self.setObjectName("PypeCreatorInput") + self.setObjectName(name) self.setWindowFlags( QtCore.Qt.Window @@ -25,18 +24,25 @@ class Universal_widget(QtWidgets.QDialog): | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle("CreatorInput") + self.setWindowTitle(name or "Pype Creator Input") # Where inputs and labels are set - content_widget = QtWidgets.QWidget(self) - self.content_layout = QtWidgets.QFormLayout(content_widget) - self.content_layout.setObjectName("ContentLayout") + self.content_widget = [QtWidgets.QWidget(self)] + top_layout = QtWidgets.QFormLayout(self.content_widget[0]) + top_layout.setObjectName("ContentLayout") + top_layout.addWidget(Spacer(5, self)) # first add widget tag line - self.create_row("QLabel", name) + top_layout.addWidget(QtWidgets.QLabel(info)) + + top_layout.addWidget(Spacer(5, self)) + + # main dynamic layout + self.content_widget.append(QtWidgets.QWidget(self)) + content_layout = QtWidgets.QFormLayout(self.content_widget[-1]) # add preset data into input widget layout - self.add_presets_to_layout(presets) + self.items = self.add_presets_to_layout(content_layout, presets) # Confirmation buttons btns_widget = QtWidgets.QWidget(self) @@ -50,10 +56,13 @@ class Universal_widget(QtWidgets.QDialog): # Main layout of the dialog main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(10, 20, 10, 20) + main_layout.setContentsMargins(10, 10, 10, 10) main_layout.setSpacing(0) - main_layout.addWidget(content_widget) + # adding content widget + for w in self.content_widget: + main_layout.addWidget(w) + main_layout.addWidget(btns_widget) ok_btn.clicked.connect(self._on_ok_clicked) @@ -63,28 +72,34 @@ class Universal_widget(QtWidgets.QDialog): self.setStyleSheet(stylesheet) def _on_ok_clicked(self): - self.value() + self.result = self.value(self.items) self.close() def _on_cancel_clicked(self): self.result = None self.close() - def value(self): - for k, v in self.items.items(): - if getattr(v, "value", None): + def value(self, data): + for k, v in data.items(): + if isinstance(v, dict): + print(f"nested: {k}") + data[k] = self.value(v) + elif getattr(v, "value", None): + print(f"normal int: {k}") result = getattr(v, "value") + data[k] = result() else: + print(f"normal text: {k}") result = getattr(v, "text") - self.items[k] = result() - self.result = self.items + data[k] = result() + return data def camel_case_split(self, text): matches = re.finditer( '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text) return " ".join([str(m.group(0)).capitalize() for m in matches]) - def create_row(self, type, text, **kwargs): + def create_row(self, layout, type, text, **kwargs): # get type attribute from qwidgets attr = getattr(QtWidgets, type) @@ -93,6 +108,7 @@ class Universal_widget(QtWidgets.QDialog): # assign the new text to lable widget label = QtWidgets.QLabel(label_text) + label.setObjectName("LineLabel") # create attribute name text strip of spaces attr_name = text.replace(" ", "") @@ -110,25 +126,57 @@ class Universal_widget(QtWidgets.QDialog): func_attr = getattr(item, func) func_attr(val) - self.content_layout.addRow(label, item) + # add to layout + layout.addRow(label, item) + return item - def add_presets_to_layout(self, data): + def add_presets_to_layout(self, content_layout, data): for k, v in data.items(): if isinstance(v, dict): - # if nested dict then create label - # TODO: create also new layout - self.create_row("QLabel", k) - self.add_presets_to_layout(v) - elif isinstance(v, str): - item = self.create_row("QLineEdit", k, setText=v) - elif isinstance(v, int): - item = self.create_row("QSpinBox", k, setValue=v) + # adding spacer between sections + self.content_widget.append(QtWidgets.QWidget(self)) + devider = QtWidgets.QVBoxLayout(self.content_widget[-1]) + devider.addWidget(Spacer(5, self)) + devider.setObjectName("Devider") - # add it to items for later requests - self.items.update({ - k: item - }) + # adding nested layout with label + self.content_widget.append(QtWidgets.QWidget(self)) + nested_content_layout = QtWidgets.QFormLayout( + self.content_widget[-1]) + nested_content_layout.setObjectName("NestedContentLayout") + + # add nested key as label + self.create_row(nested_content_layout, "QLabel", k) + data[k] = self.add_presets_to_layout(nested_content_layout, v) + elif isinstance(v, str): + print(f"layout.str: {k}") + print(f"content_layout: {content_layout}") + data[k] = self.create_row( + content_layout, "QLineEdit", k, setText=v) + elif isinstance(v, int): + print(f"layout.int: {k}") + print(f"content_layout: {content_layout}") + data[k] = self.create_row( + content_layout, "QSpinBox", k, setValue=v) + return data + + +class Spacer(QtWidgets.QWidget): + def __init__(self, height, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + self.setFixedHeight(height) + + real_spacer = QtWidgets.QWidget(self) + real_spacer.setObjectName("Spacer") + real_spacer.setFixedHeight(height) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(real_spacer) + + self.setLayout(layout) def get_reference_node_parents(ref): diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 18759a2f98..43207743e2 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -11,7 +11,8 @@ class CreateShotClip(resolve.Creator): icon = "film" defaults = ["Main"] - gui_name = "Define sequencial rename" + gui_name = "Pype sequencial rename with hirerarchy" + gui_info = "Define sequencial rename and fill hierarchy data." presets = None def process(self): @@ -20,14 +21,11 @@ class CreateShotClip(resolve.Creator): if len(self.selected) < 1: return - widget = self.widget(self.gui_name, self.presets) + widget = self.widget(self.gui_name, self.gui_info, self.presets) widget.exec_() - print(widget.result) - if widget.result: - print("success") - return - else: + if not widget.result: + print("Operation aborted") return # sequence attrs @@ -43,8 +41,15 @@ class CreateShotClip(resolve.Creator): lib.rename_add = 0 for i, t_data in enumerate(self.selected): lib.rename_index = i - print(t_data) + + # clear color after it is done + t_data["clip"]["item"].ClearClipColor() + # convert track item to timeline media pool item c_clip = resolve.create_compound_clip( - t_data, mp_folder, rename=True, **dict( - {"presets": self.presets})) + t_data, + mp_folder, + rename=True, + **dict( + {"presets": widget.result}) + ) From 87715afcb80061157a25edfa4913c7dcf398d374 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 30 Jun 2020 14:45:09 +0100 Subject: [PATCH 025/947] Increase socket thread timeout. --- pype/modules/ftrack/ftrack_server/socket_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/socket_thread.py b/pype/modules/ftrack/ftrack_server/socket_thread.py index dda4c7db35..3602f2f138 100644 --- a/pype/modules/ftrack/ftrack_server/socket_thread.py +++ b/pype/modules/ftrack/ftrack_server/socket_thread.py @@ -11,7 +11,7 @@ from pype.api import Logger class SocketThread(threading.Thread): """Thread that checks suprocess of storer of processor of events""" - MAX_TIMEOUT = 35 + MAX_TIMEOUT = 120 def __init__(self, name, port, filepath, additional_args=[]): super(SocketThread, self).__init__() From 5df2c326142b5434339e3b56d9887cc8816bee2f Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Fri, 3 Jul 2020 17:16:26 +0200 Subject: [PATCH 026/947] WIP POC imlementation of Websocket Json RPC server Includes two testing clients Requires: - adding wsrpc-aiohttp==3.0.1 to pypeapp/requirements.txt - adding code to \pype-config\presets\tray\menu_items.json , { "title": "Websocket Server", "type": "module", "import_path": "pype.modules.websocket_server", "fromlist": ["pype","modules"] } --- pype/modules/websocket_server/__init__.py | 5 + .../websocket_server/external_app_1.py | 46 +++++ .../test_client/wsrpc_client.html | 179 ++++++++++++++++++ .../test_client/wsrpc_client.py | 34 ++++ .../websocket_server/websocket_server.py | 129 +++++++++++++ 5 files changed, 393 insertions(+) create mode 100644 pype/modules/websocket_server/__init__.py create mode 100644 pype/modules/websocket_server/external_app_1.py create mode 100644 pype/modules/websocket_server/test_client/wsrpc_client.html create mode 100644 pype/modules/websocket_server/test_client/wsrpc_client.py create mode 100644 pype/modules/websocket_server/websocket_server.py diff --git a/pype/modules/websocket_server/__init__.py b/pype/modules/websocket_server/__init__.py new file mode 100644 index 0000000000..eb5a0d9f27 --- /dev/null +++ b/pype/modules/websocket_server/__init__.py @@ -0,0 +1,5 @@ +from .websocket_server import WebSocketServer + + +def tray_init(tray_widget, main_widget): + return WebSocketServer() diff --git a/pype/modules/websocket_server/external_app_1.py b/pype/modules/websocket_server/external_app_1.py new file mode 100644 index 0000000000..34a43a4d23 --- /dev/null +++ b/pype/modules/websocket_server/external_app_1.py @@ -0,0 +1,46 @@ +import asyncio + +from pype.api import Logger +from wsrpc_aiohttp import WebSocketRoute + +log = Logger().get_logger("WebsocketServer") + +class ExternalApp1(WebSocketRoute): + """ + One route, mimicking external application (like Harmony, etc). + All functions could be called from client. + 'do_notify' function calls function on the client - mimicking + notification after long running job on the server or similar + """ + + def init(self, **kwargs): + # Python __init__ must be return "self". + # This method might return anything. + log.debug("someone called ExternalApp1 route") + return kwargs + + async def server_function_one(self): + log.info('In function one') + + async def server_function_two(self): + log.info('In function two') + return 'function two' + + async def server_function_three(self): + log.info('In function three') + asyncio.ensure_future(self.do_notify()) + return '{"message":"function tree"}' + + async def server_function_four(self, *args, **kwargs): + log.info('In function four args {} kwargs {}'.format(args, kwargs)) + ret = dict(**kwargs) + ret["message"] = "function four received arguments" + return str(ret) + + # This method calls function on the client side + async def do_notify(self): + import time + time.sleep(5) + log.info('Calling function on server after delay') + awesome = 'Somebody server_function_three method!' + await self.socket.call('notify', result=awesome) diff --git a/pype/modules/websocket_server/test_client/wsrpc_client.html b/pype/modules/websocket_server/test_client/wsrpc_client.html new file mode 100644 index 0000000000..9c3f469aca --- /dev/null +++ b/pype/modules/websocket_server/test_client/wsrpc_client.html @@ -0,0 +1,179 @@ + + + + + Title + + + + + + + + + + + + + +
+
Test of wsrpc javascript client
+ +
+ +
+
+
+
+

No return value

+
+
+
    +
  • Calls server_function_one
  • +
  • Function only logs on server
  • +
  • No return value
  • +
  •  
  • +
  •  
  • +
  •  
  • +
+ +
+
+
+
+

Return value

+
+
+
    +
  • Calls server_function_two
  • +
  • Function logs on server
  • +
  • Returns simple text value
  • +
  •  
  • +
  •  
  • +
  •  
  • +
+ +
+
+
+
+

Notify

+
+
+
    +
  • Calls server_function_three
  • +
  • Function logs on server
  • +
  • Returns json payload
  • +
  • Server then calls function ON the client after delay
  • +
  •  
  • +
+ +
+
+
+
+

Send value

+
+
+
    +
  • Calls server_function_four
  • +
  • Function logs on server
  • +
  • Returns modified sent values
  • +
  •  
  • +
  •  
  • +
  •  
  • +
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/pype/modules/websocket_server/test_client/wsrpc_client.py b/pype/modules/websocket_server/test_client/wsrpc_client.py new file mode 100644 index 0000000000..ef861513ae --- /dev/null +++ b/pype/modules/websocket_server/test_client/wsrpc_client.py @@ -0,0 +1,34 @@ +import asyncio + +from wsrpc_aiohttp import WSRPCClient + +""" + Simple testing Python client for wsrpc_aiohttp + Calls sequentially multiple methods on server +""" + +loop = asyncio.get_event_loop() + + +async def main(): + print("main") + client = WSRPCClient("ws://127.0.0.1:8099/ws/", + loop=asyncio.get_event_loop()) + + client.add_route('notify', notify) + await client.connect() + print("connected") + print(await client.proxy.ExternalApp1.server_function_one()) + print(await client.proxy.ExternalApp1.server_function_two()) + print(await client.proxy.ExternalApp1.server_function_three()) + print(await client.proxy.ExternalApp1.server_function_four(foo="one")) + await client.close() + + +def notify(socket, *args, **kwargs): + print("called from server") + + +if __name__ == "__main__": + # loop.run_until_complete(main()) + asyncio.run(main()) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py new file mode 100644 index 0000000000..ce5f23180a --- /dev/null +++ b/pype/modules/websocket_server/websocket_server.py @@ -0,0 +1,129 @@ +from pype.api import config, Logger +from Qt import QtCore + +from aiohttp import web, WSCloseCode +import asyncio +import weakref +from wsrpc_aiohttp import STATIC_DIR, WebSocketAsync + +from . import external_app_1 + +log = Logger().get_logger("WebsocketServer") + +class WebSocketServer(): + """ + Basic POC implementation of asychronic websocket RPC server. + Uses class in external_app_1.py to mimic implementation for single + external application. + 'test_client' folder contains two test implementations of client + + WIP + """ + + def __init__(self): + self.qaction = None + self.failed_icon = None + self._is_running = False + default_port = 8099 + + try: + self.presets = config.get_presets()["services"]["websocket_server"] + except Exception: + self.presets = {"default_port": default_port, "exclude_ports": []} + log.debug(( + "There are not set presets for WebsocketServer." + " Using defaults \"{}\"" + ).format(str(self.presets))) + + self.app = web.Application() + self.app["websockets"] = weakref.WeakSet() + + self.app.router.add_route("*", "/ws/", WebSocketAsync) + self.app.router.add_static("/js", STATIC_DIR) + self.app.router.add_static("/", ".") + + # add route with multiple methods for single "external app" + WebSocketAsync.add_route('ExternalApp1', external_app_1.ExternalApp1) + + self.app.on_shutdown.append(self.on_shutdown) + + self.websocket_thread = WebsocketServerThread(self, default_port) + + + def add_routes_for_class(self, cls): + ''' Probably obsolete, use classes inheriting from WebSocketRoute ''' + methods = [method for method in dir(cls) if '__' not in method] + log.info("added routes for {}".format(methods)) + for method in methods: + WebSocketAsync.add_route(method, getattr(cls, method)) + + def tray_start(self): + self.websocket_thread.start() + + # log.info("Starting websocket server") + # loop = asyncio.get_event_loop() + # self.runner = web.AppRunner(self.app) + # loop.run_until_complete(self.runner.setup()) + # self.site = web.TCPSite(self.runner, 'localhost', 8044) + # loop.run_until_complete(self.site.start()) + # log.info('site {}'.format(self.site._server)) + # asyncio.ensure_future() + # #loop.run_forever() + # #web.run_app(self.app, port=8044) + # log.info("Started websocket server") + + @property + def is_running(self): + return self.websocket_thread.is_running + + def stop(self): + self.websocket_thread.is_running = False + + def thread_stopped(self): + self._is_running = False + + async def on_shutdown(self): + """ + Gracefully remove all connected websocket connections + :return: None + """ + log.info('Shutting down websocket server') + for ws in set(self.app['websockets']): + await ws.close(code=WSCloseCode.GOING_AWAY, + message='Server shutdown') + +class WebsocketServerThread(QtCore.QThread): + """ Listener for websocket rpc requests. + + It would be probably better to "attach" this to main thread (as for + example Harmony needs to run something on main thread), but currently + it creates separate thread and separate asyncio event loop + """ + def __init__(self, module, port): + super(WebsocketServerThread, self).__init__() + self.is_running = False + self.port = port + self.module = module + + def run(self): + self.is_running = True + + try: + log.debug( + "Running Websocket server on URL:" + " \"ws://localhost:{}\"".format(self.port) + ) + + log.info("Starting websocket server") + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + web.run_app(self.module.app, port=self.port) # blocking + log.info("Started websocket server") + + except Exception: + log.warning( + "Websocket Server service has failed", exc_info=True + ) + + self.is_running = False + self.module.thread_stopped() From d6485030b5d3f1f19f1c7535fffd00ff2bf5d934 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Fri, 3 Jul 2020 17:31:53 +0200 Subject: [PATCH 027/947] Hound --- pype/modules/websocket_server/external_app_1.py | 1 + pype/modules/websocket_server/websocket_server.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pype/modules/websocket_server/external_app_1.py b/pype/modules/websocket_server/external_app_1.py index 34a43a4d23..9352787175 100644 --- a/pype/modules/websocket_server/external_app_1.py +++ b/pype/modules/websocket_server/external_app_1.py @@ -5,6 +5,7 @@ from wsrpc_aiohttp import WebSocketRoute log = Logger().get_logger("WebsocketServer") + class ExternalApp1(WebSocketRoute): """ One route, mimicking external application (like Harmony, etc). diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index ce5f23180a..29b08a1c6d 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -10,6 +10,7 @@ from . import external_app_1 log = Logger().get_logger("WebsocketServer") + class WebSocketServer(): """ Basic POC implementation of asychronic websocket RPC server. @@ -31,8 +32,8 @@ class WebSocketServer(): except Exception: self.presets = {"default_port": default_port, "exclude_ports": []} log.debug(( - "There are not set presets for WebsocketServer." - " Using defaults \"{}\"" + "There are not set presets for WebsocketServer." + " Using defaults \"{}\"" ).format(str(self.presets))) self.app = web.Application() @@ -49,9 +50,8 @@ class WebSocketServer(): self.websocket_thread = WebsocketServerThread(self, default_port) - def add_routes_for_class(self, cls): - ''' Probably obsolete, use classes inheriting from WebSocketRoute ''' + """ Probably obsolete, use classes inheriting from WebSocketRoute """ methods = [method for method in dir(cls) if '__' not in method] log.info("added routes for {}".format(methods)) for method in methods: @@ -92,6 +92,7 @@ class WebSocketServer(): await ws.close(code=WSCloseCode.GOING_AWAY, message='Server shutdown') + class WebsocketServerThread(QtCore.QThread): """ Listener for websocket rpc requests. From a29c84e9af7180df2a2b12b23bf1d79a91d13315 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Wed, 8 Jul 2020 16:14:07 +0200 Subject: [PATCH 028/947] Fix - changed usage of QThread to threading.Thread Apparently QThread is making problems on some non-gui linux distros --- pype/modules/websocket_server/websocket_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 29b08a1c6d..de16891ee1 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -1,6 +1,6 @@ from pype.api import config, Logger -from Qt import QtCore +import threading from aiohttp import web, WSCloseCode import asyncio import weakref @@ -93,7 +93,7 @@ class WebSocketServer(): message='Server shutdown') -class WebsocketServerThread(QtCore.QThread): +class WebsocketServerThread(threading.Thread): """ Listener for websocket rpc requests. It would be probably better to "attach" this to main thread (as for From d02e9596a665188249e5f096af21c644f71edd5d Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Thu, 9 Jul 2020 12:30:55 +0200 Subject: [PATCH 029/947] Added separate 'hosts' folder Added auto-routing of classes from selected folders --- .../websocket_server/hosts/__init__.py | 0 .../{ => hosts}/external_app_1.py | 0 .../websocket_server/websocket_server.py | 45 ++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 pype/modules/websocket_server/hosts/__init__.py rename pype/modules/websocket_server/{ => hosts}/external_app_1.py (100%) diff --git a/pype/modules/websocket_server/hosts/__init__.py b/pype/modules/websocket_server/hosts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/modules/websocket_server/external_app_1.py b/pype/modules/websocket_server/hosts/external_app_1.py similarity index 100% rename from pype/modules/websocket_server/external_app_1.py rename to pype/modules/websocket_server/hosts/external_app_1.py diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index de16891ee1..8c8cb7be67 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -6,7 +6,10 @@ import asyncio import weakref from wsrpc_aiohttp import STATIC_DIR, WebSocketAsync -from . import external_app_1 +import os +import sys +import pyclbr +import importlib log = Logger().get_logger("WebsocketServer") @@ -32,8 +35,8 @@ class WebSocketServer(): except Exception: self.presets = {"default_port": default_port, "exclude_ports": []} log.debug(( - "There are not set presets for WebsocketServer." - " Using defaults \"{}\"" + "There are not set presets for WebsocketServer." + " Using defaults \"{}\"" ).format(str(self.presets))) self.app = web.Application() @@ -44,18 +47,40 @@ class WebSocketServer(): self.app.router.add_static("/", ".") # add route with multiple methods for single "external app" - WebSocketAsync.add_route('ExternalApp1', external_app_1.ExternalApp1) + directories_with_routes = ['hosts'] + self.add_routes_for_directories(directories_with_routes) self.app.on_shutdown.append(self.on_shutdown) self.websocket_thread = WebsocketServerThread(self, default_port) - def add_routes_for_class(self, cls): - """ Probably obsolete, use classes inheriting from WebSocketRoute """ - methods = [method for method in dir(cls) if '__' not in method] - log.info("added routes for {}".format(methods)) - for method in methods: - WebSocketAsync.add_route(method, getattr(cls, method)) + def add_routes_for_directories(self, directories_with_routes): + """ Loops through selected directories to find all modules and + in them all classes implementing 'WebSocketRoute' that could be + used as route. + All methods in these classes are registered automatically. + """ + for dir_name in directories_with_routes: + dir_name = os.path.join(os.path.dirname(__file__), dir_name) + for file_name in os.listdir(dir_name): + if '.py' in file_name and '__' not in file_name: + self.add_routes_for_module(file_name, dir_name) + + def add_routes_for_module(self, file_name, dir_name): + """ Auto routes for all classes implementing 'WebSocketRoute' + in 'file_name' in 'dir_name' + """ + module_name = file_name.replace('.py', '') + module_info = pyclbr.readmodule(module_name, [dir_name]) + + for class_name, cls_object in module_info.items(): + sys.path.append(dir_name) + if 'WebSocketRoute' in cls_object.super: + log.debug('Adding route for {}'.format(class_name)) + module = importlib.import_module(module_name) + cls = getattr(module, class_name) + WebSocketAsync.add_route(class_name, cls) + sys.path.pop() def tray_start(self): self.websocket_thread.start() From 359fa35a454277f9cc734616ff4b38b394a65d63 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 9 Jul 2020 15:32:10 +0200 Subject: [PATCH 030/947] simplify nuke family collection --- pype/plugins/global/publish/integrate_new.py | 2 ++ .../global/publish/submit_publish_job.py | 7 ------- .../plugins/nuke/publish/collect_instances.py | 21 +++++++++++-------- pype/plugins/nuke/publish/collect_writes.py | 9 -------- .../nuke/publish/extract_render_local.py | 7 ++++--- 5 files changed, 18 insertions(+), 28 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 0ec8045448..6d5c693b79 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -144,6 +144,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if task_name: anatomy_data["task"] = task_name + anatomy_data["family"] = instance.data.get("family") + stagingdir = instance.data.get("stagingDir") if not stagingdir: self.log.info(( diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 7a73e921e2..10c01886fa 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -686,13 +686,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if item in instance.data.get("families", []): instance_skeleton_data["families"] += [item] - if "render.farm" in instance.data["families"]: - instance_skeleton_data.update({ - "family": "render2d", - "families": ["render"] + [f for f in instance.data["families"] - if "render.farm" not in f] - }) - # transfer specific properties from original instance based on # mapping dictionary `instance_transfer` for key, values in self.instance_transfer.items(): diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 54891d189c..0bbede11c0 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -55,6 +55,12 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): families_ak = avalon_knob_data.get("families") families = list() + if families_ak: + families.append(families_ak) + + families.append(family) + + # except disabled nodes but exclude backdrops in test if ("nukenodes" not in family) and (node["disable"].value()): continue @@ -70,18 +76,19 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): if node.Class() == "Group": # only alter families for render family if "write" in families_ak: + if node["render"].value(): self.log.info("flagged for render") - add_family = "{}.local".format(family) + add_family = "{}.local".format("render") # dealing with local/farm rendering if node["render_farm"].value(): self.log.info("adding render farm family") - add_family = "{}.farm".format(family) + add_family = "{}.farm".format("render") instance.data["transfer"] = False families.append(add_family) - else: - # add family into families - families.insert(0, family) + if "render" in families: + families.remove("render") + family = "write" node.begin() for i in nuke.allNodes(): @@ -90,10 +97,6 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): self.log.debug("__ families: `{}`".format(families)) - if families_ak: - families.append(families_ak) - else: - families.append(family) # Get format format = root['format'].value() diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index c70953d23f..fb00aeb1ae 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -118,7 +118,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "colorspace": node["colorspace"].value(), } - instance.data["family"] = "write" group_node = [x for x in instance if x.Class() == "Group"][0] deadlineChunkSize = 1 if "deadlineChunkSize" in group_node.knobs(): @@ -128,8 +127,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): if "deadlinePriority" in group_node.knobs(): deadlinePriority = group_node["deadlinePriority"].value() - families = [f for f in instance.data["families"] if "write" not in f] - instance.data.update({ "versionData": version_data, "path": path, @@ -143,18 +140,12 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "frameStartHandle": first_frame, "frameEndHandle": last_frame, "outputType": output_type, - "family": "write", "families": families, "colorspace": node["colorspace"].value(), "deadlineChunkSize": deadlineChunkSize, "deadlinePriority": deadlinePriority }) - if "render" in families: - instance.data["family"] = "render2d" - if "render" not in families: - instance.data["families"].insert(0, "render") - if "prerender" in families: instance.data.update({ "family": "prerender", diff --git a/pype/plugins/nuke/publish/extract_render_local.py b/pype/plugins/nuke/publish/extract_render_local.py index 37a6701380..79662d62a8 100644 --- a/pype/plugins/nuke/publish/extract_render_local.py +++ b/pype/plugins/nuke/publish/extract_render_local.py @@ -82,9 +82,9 @@ class NukeRenderLocal(pype.api.Extractor): # redefinition of families if "render.local" in families: - instance.data['family'] = 'render2d' + instance.data['family'] = 'render' families.remove('render.local') - families.insert(0, "render") + families.insert(0, "render2d") elif "prerender.local" in families: instance.data['family'] = 'prerender' families.remove('prerender.local') @@ -99,4 +99,5 @@ class NukeRenderLocal(pype.api.Extractor): instance.data['collection'] = collection self.log.info('Finished render') - return + + self.log.debug("instance extracted: {}".format(instance.data)) From 0d402b857cb24d1b0f35a9e1d8d9bb10f868f086 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:08:55 +0200 Subject: [PATCH 031/947] ftrack applications registration is based on presets --- .../actions/action_application_loader.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pype/modules/ftrack/actions/action_application_loader.py b/pype/modules/ftrack/actions/action_application_loader.py index ec7fc53fb6..b3166e850e 100644 --- a/pype/modules/ftrack/actions/action_application_loader.py +++ b/pype/modules/ftrack/actions/action_application_loader.py @@ -3,8 +3,7 @@ import toml import time from pype.modules.ftrack.lib import AppAction from avalon import lib -from pype.api import Logger -from pype.lib import get_all_avalon_projects +from pype.api import Logger, config log = Logger().get_logger(__name__) @@ -49,17 +48,26 @@ def registerApp(app, session, plugins_presets): def register(session, plugins_presets={}): - # WARNING getting projects only helps to check connection to mongo - # - without will `discover` of ftrack apps actions take ages - result = get_all_avalon_projects() + app_usages = ( + config.get_presets() + .get("global", {}) + .get("applications") + ) or {} apps = [] - + missing_app_names = [] launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") for file in os.listdir(launchers_path): filename, ext = os.path.splitext(file) if ext.lower() != ".toml": continue + + app_usage = app_usages.get(filename) + if not app_usage: + if app_usage is None: + missing_app_names.append(filename) + continue + loaded_data = toml.load(os.path.join(launchers_path, file)) app_data = { "name": filename, @@ -67,7 +75,7 @@ def register(session, plugins_presets={}): } apps.append(app_data) - apps = sorted(apps, key=lambda x: x['name']) + apps = sorted(apps, key=lambda app: app["name"]) app_counter = 0 for app in apps: try: @@ -76,7 +84,7 @@ def register(session, plugins_presets={}): time.sleep(0.1) app_counter += 1 except Exception as exc: - log.exception( + log.warning( "\"{}\" - not a proper App ({})".format(app['name'], str(exc)), exc_info=True ) From cedf2920f0962af7dbb3f31e4e6e2bdee7fe61f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:09:09 +0200 Subject: [PATCH 032/947] missing apps are logged out --- .../ftrack/actions/action_application_loader.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/modules/ftrack/actions/action_application_loader.py b/pype/modules/ftrack/actions/action_application_loader.py index b3166e850e..ecc5a4fad3 100644 --- a/pype/modules/ftrack/actions/action_application_loader.py +++ b/pype/modules/ftrack/actions/action_application_loader.py @@ -75,6 +75,16 @@ def register(session, plugins_presets={}): } apps.append(app_data) + if missing_app_names: + log.debug( + "Apps not defined in applications usage. ({})".format( + ", ".join(( + "\"{}\"".format(app_name) + for app_name in missing_app_names + )) + ) + ) + apps = sorted(apps, key=lambda app: app["name"]) app_counter = 0 for app in apps: From 15cef44eeabb25df86492176048caa6e5a5fd750 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:13:27 +0200 Subject: [PATCH 033/947] method custom_attributes_from_file split into 2 parts --- .../actions/action_create_cust_attrs.py | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 9845cc8876..19617a7254 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -144,7 +144,7 @@ class CustomAttributes(BaseAction): try: self.prepare_global_data(session) self.avalon_mongo_id_attributes(session, event) - self.custom_attributes_from_file(session, event) + self.custom_attributes_from_file(event) job['status'] = 'done' session.commit() @@ -335,36 +335,40 @@ class CustomAttributes(BaseAction): exc_info=True ) - def custom_attributes_from_file(self, session, event): - presets = config.get_presets()['ftrack']['ftrack_custom_attributes'] - for cust_attr_data in presets: - cust_attr_name = cust_attr_data.get( - 'label', - cust_attr_data.get('key') ) - try: - data = {} - # Get key, label, type - data.update(self.get_required(cust_attr_data)) - # Get hierachical/ entity_type/ object_id - data.update(self.get_entity_type(cust_attr_data)) - # Get group, default, security roles - data.update(self.get_optional(cust_attr_data)) - # Process data - self.process_attribute(data) - except CustAttrException as cae: - if cust_attr_name: - msg = 'Custom attribute error "{}" - {}'.format( - cust_attr_name, str(cae) - ) - else: - msg = 'Custom attribute error - {}'.format(str(cae)) - self.log.warning(msg, exc_info=True) - self.show_message(event, msg) + def custom_attributes_from_file(self, event): + presets = config.get_presets()["ftrack"]["ftrack_custom_attributes"] + for cust_attr_data in presets: + self.process_attr_data(cust_attr_data, event) - return True + def process_attr_data(self, cust_attr_data, event): + cust_attr_name = cust_attr_data.get( + "label", + cust_attr_data.get("key") + ) + + try: + data = {} + # Get key, label, type + data.update(self.get_required(cust_attr_data)) + # Get hierachical/ entity_type/ object_id + data.update(self.get_entity_type(cust_attr_data)) + # Get group, default, security roles + data.update(self.get_optional(cust_attr_data)) + # Process data + self.process_attribute(data) + + except CustAttrException as cae: + if cust_attr_name: + msg = 'Custom attribute error "{}" - {}'.format( + cust_attr_name, str(cae) + ) + else: + msg = 'Custom attribute error - {}'.format(str(cae)) + self.log.warning(msg, exc_info=True) + self.show_message(event, msg) def process_attribute(self, data): existing_atr = self.session.query('CustomAttributeConfiguration').all() From f06c184d5cc5e88340754735181c8f021b78f858 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:15:34 +0200 Subject: [PATCH 034/947] added application_definitions method which return app name with labels --- .../actions/action_create_cust_attrs.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 19617a7254..3378fe5aa3 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -335,8 +335,48 @@ class CustomAttributes(BaseAction): exc_info=True ) + def application_definitions(self): + app_usages = ( + config.get_presets() + .get("global", {}) + .get("applications") + ) or {} + app_definitions = [] + launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") + + missing_app_names = [] + for file in os.listdir(launchers_path): + app_name, ext = os.path.splitext(file) + if ext.lower() != ".toml": + continue + + if not app_usages.get(app_name): + missing_app_names.append(app_name) + continue + + loaded_data = toml.load(os.path.join(launchers_path, file)) + + ftrack_label = loaded_data.get("ftrack_label") + if ftrack_label: + parts = app_name.split("_") + if len(parts) > 1: + ftrack_label = " ".join((ftrack_label, parts[-1])) + else: + ftrack_label = loaded_data.get("label", app_name) + + app_definitions.append({app_name: ftrack_label}) + + if missing_app_names: + self.log.warning( + "Apps not defined in applications usage. ({})".format( + ", ".join(( + "\"{}\"".format(app_name) + for app_name in missing_app_names + )) + ) ) + return app_definitions def custom_attributes_from_file(self, event): presets = config.get_presets()["ftrack"]["ftrack_custom_attributes"] From f50d46ba3155de1eace4ecf30c14618473104889 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:16:36 +0200 Subject: [PATCH 035/947] applications custom attribute is hardcoded in create/upadate action --- .../ftrack/actions/action_create_cust_attrs.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 3378fe5aa3..b635abb8a7 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -1,4 +1,6 @@ +import os import collections +import toml import json import arrow import ftrack_api @@ -144,6 +146,7 @@ class CustomAttributes(BaseAction): try: self.prepare_global_data(session) self.avalon_mongo_id_attributes(session, event) + self.applications_attribute(event) self.custom_attributes_from_file(event) job['status'] = 'done' @@ -378,6 +381,20 @@ class CustomAttributes(BaseAction): ) return app_definitions + def applications_attribute(self, event): + applications_custom_attr_data = { + "label": "Applications", + "key": "applications", + "type": "enumerator", + "entity_type": "show", + "group": "avalon", + "config": { + "multiselect": True, + "data": self.application_definitions() + } + } + self.process_attr_data(applications_custom_attr_data, event) + def custom_attributes_from_file(self, event): presets = config.get_presets()["ftrack"]["ftrack_custom_attributes"] for cust_attr_data in presets: From a020a661f39e6549940f146ffdc59b040eae12ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:17:41 +0200 Subject: [PATCH 036/947] make sure applications are not created twice --- pype/modules/ftrack/actions/action_create_cust_attrs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index b635abb8a7..cbc551ac88 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -398,7 +398,9 @@ class CustomAttributes(BaseAction): def custom_attributes_from_file(self, event): presets = config.get_presets()["ftrack"]["ftrack_custom_attributes"] for cust_attr_data in presets: - self.process_attr_data(cust_attr_data, event) + key = applications.get("key") + if key != "applications": + self.process_attr_data(cust_attr_data, event) def process_attr_data(self, cust_attr_data, event): cust_attr_name = cust_attr_data.get( From e167e59001da47310076f80c2d6a84ad137a0eeb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Jul 2020 18:17:56 +0200 Subject: [PATCH 037/947] removed unused fucntion from pype.lib --- pype/lib.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index 87808e53f5..b62840f3a4 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -520,14 +520,6 @@ def set_io_database(): io.install() -def get_all_avalon_projects(): - db = get_avalon_database() - projects = [] - for name in db.collection_names(): - projects.append(db[name].find_one({'type': 'project'})) - return projects - - def filter_pyblish_plugins(plugins): """ This servers as plugin filter / modifier for pyblish. It will load plugin From dc7a1042145b9eb3ee5c20424b8c352a3e13affb Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Fri, 10 Jul 2020 11:15:46 +0200 Subject: [PATCH 038/947] Added proper shutdown --- .../websocket_server/websocket_server.py | 98 ++++++++++++------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 8c8cb7be67..87f9bde8e0 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -1,9 +1,8 @@ from pype.api import config, Logger import threading -from aiohttp import web, WSCloseCode +from aiohttp import web import asyncio -import weakref from wsrpc_aiohttp import STATIC_DIR, WebSocketAsync import os @@ -40,7 +39,6 @@ class WebSocketServer(): ).format(str(self.presets))) self.app = web.Application() - self.app["websockets"] = weakref.WeakSet() self.app.router.add_route("*", "/ws/", WebSocketAsync) self.app.router.add_static("/js", STATIC_DIR) @@ -50,8 +48,6 @@ class WebSocketServer(): directories_with_routes = ['hosts'] self.add_routes_for_directories(directories_with_routes) - self.app.on_shutdown.append(self.on_shutdown) - self.websocket_thread = WebsocketServerThread(self, default_port) def add_routes_for_directories(self, directories_with_routes): @@ -85,38 +81,33 @@ class WebSocketServer(): def tray_start(self): self.websocket_thread.start() - # log.info("Starting websocket server") - # loop = asyncio.get_event_loop() - # self.runner = web.AppRunner(self.app) - # loop.run_until_complete(self.runner.setup()) - # self.site = web.TCPSite(self.runner, 'localhost', 8044) - # loop.run_until_complete(self.site.start()) - # log.info('site {}'.format(self.site._server)) - # asyncio.ensure_future() - # #loop.run_forever() - # #web.run_app(self.app, port=8044) - # log.info("Started websocket server") + def tray_exit(self): + self.stop() + + def stop_websocket_server(self): + + self.stop() @property def is_running(self): return self.websocket_thread.is_running def stop(self): - self.websocket_thread.is_running = False + if not self.is_running: + return + try: + log.debug("Stopping websocket server") + self.websocket_thread.is_running = False + self.websocket_thread.stop() + except Exception: + log.warning( + "Error has happened during Killing websocket server", + exc_info=True + ) def thread_stopped(self): self._is_running = False - async def on_shutdown(self): - """ - Gracefully remove all connected websocket connections - :return: None - """ - log.info('Shutting down websocket server') - for ws in set(self.app['websockets']): - await ws.close(code=WSCloseCode.GOING_AWAY, - message='Server shutdown') - class WebsocketServerThread(threading.Thread): """ Listener for websocket rpc requests. @@ -130,26 +121,67 @@ class WebsocketServerThread(threading.Thread): self.is_running = False self.port = port self.module = module + self.loop = None + self.runner = None + self.site = None def run(self): self.is_running = True try: + log.info("Starting websocket server") + self.loop = asyncio.new_event_loop() # create new loop for thread + asyncio.set_event_loop(self.loop) + + self.loop.run_until_complete(self.start_server()) + log.debug( "Running Websocket server on URL:" " \"ws://localhost:{}\"".format(self.port) ) - log.info("Starting websocket server") - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - web.run_app(self.module.app, port=self.port) # blocking - log.info("Started websocket server") - + asyncio.ensure_future(self.check_shutdown(), loop=self.loop) + self.loop.run_forever() except Exception: log.warning( "Websocket Server service has failed", exc_info=True ) + finally: + self.loop.close() # optional self.is_running = False self.module.thread_stopped() + log.info("Websocket server stopped") + + async def start_server(self): + """ Starts runner and TCPsite """ + self.runner = web.AppRunner(self.module.app) + await self.runner.setup() + self.site = web.TCPSite(self.runner, 'localhost', self.port) + await self.site.start() + + def stop(self): + """ Sets is_running flag to false, 'check_shutdown' shuts server down""" + self.is_running = False + + async def check_shutdown(self): + """ Future that is running and checks if server should be running + periodically. + """ + while self.is_running: + await asyncio.sleep(0.5) + + log.debug("Starting shutdown") + await self.site.stop() + log.debug("Site stopped") + await self.runner.cleanup() + log.debug("Runner stopped") + tasks = [task for task in asyncio.all_tasks() if + task is not asyncio.current_task()] + list(map(lambda task: task.cancel(), tasks)) # cancel all the tasks + results = await asyncio.gather(*tasks, return_exceptions=True) + log.debug(f'Finished awaiting cancelled tasks, results: {results}...') + await self.loop.shutdown_asyncgens() + # to really make sure everything else has time to stop + await asyncio.sleep(0.07) + self.loop.stop() From 75ecb02262ef11e43b5ef5164cffc4c14cad08e5 Mon Sep 17 00:00:00 2001 From: "petr.kalis" Date: Fri, 10 Jul 2020 11:25:22 +0200 Subject: [PATCH 039/947] Hound --- pype/modules/websocket_server/websocket_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/websocket_server/websocket_server.py b/pype/modules/websocket_server/websocket_server.py index 87f9bde8e0..56e71ea895 100644 --- a/pype/modules/websocket_server/websocket_server.py +++ b/pype/modules/websocket_server/websocket_server.py @@ -161,7 +161,7 @@ class WebsocketServerThread(threading.Thread): await self.site.start() def stop(self): - """ Sets is_running flag to false, 'check_shutdown' shuts server down""" + """Sets is_running flag to false, 'check_shutdown' shuts server down""" self.is_running = False async def check_shutdown(self): From 01ab07efe6780576ec4daa278a69e864edc21817 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jul 2020 17:18:19 +0200 Subject: [PATCH 040/947] ftrack custom attributes are defined in pype --- pype/modules/ftrack/lib/avalon_sync.py | 8 +++ .../modules/ftrack/lib/custom_attributes.json | 60 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 pype/modules/ftrack/lib/custom_attributes.json diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index c5c9eb9054..49e4c178c7 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -1,6 +1,7 @@ import os import re import queue +import json import collections import copy @@ -30,6 +31,13 @@ EntitySchemas = { # name of Custom attribute that stores mongo_id from avalon db CustAttrIdKey = "avalon_mongo_id" CustAttrAutoSync = "avalon_auto_sync" +def default_custom_attributes_definition(): + json_file_path = os.path.join( + os.path.dirname(__file__), "custom_attributes.json" + ) + with open(json_file_path, "r") as json_stream: + data = json.load(json_stream) + return data def check_regex(name, entity_type, in_schema=None, schema_patterns=None): diff --git a/pype/modules/ftrack/lib/custom_attributes.json b/pype/modules/ftrack/lib/custom_attributes.json new file mode 100644 index 0000000000..17ff6691d3 --- /dev/null +++ b/pype/modules/ftrack/lib/custom_attributes.json @@ -0,0 +1,60 @@ +{ + "show": { + "avalon_auto_sync": { + "label": "Avalon auto-sync", + "type": "boolean", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] + }, + "library_project": { + "label": "Library Project", + "type": "boolean", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] + } + }, + "is_hierarchical": { + "fps": { + "label": "FPS", + "type": "number", + "config": {"isdecimal": true} + }, + "clipIn": { + "label": "Clip in", + "type": "number" + }, + "clipOut": { + "label": "Clip out", + "type": "number" + }, + "frameStart": { + "label": "Frame start", + "type": "number" + }, + "frameEnd": { + "label": "Frame end", + "type": "number" + }, + "resolutionWidth": { + "label": "Resolution Width", + "type": "number" + }, + "resolutionHeight": { + "label": "Resolution Height", + "type": "number" + }, + "pixelAspect": { + "label": "Pixel aspect", + "type": "number", + "config": {"isdecimal": true} + }, + "handleStart": { + "label": "Frame handles start", + "type": "number" + }, + "handleEnd": { + "label": "Frame handles end", + "type": "number" + } + } +} From 3975c7a0e1aade0f6b17068b5c67b1c4dfd11314 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jul 2020 17:18:54 +0200 Subject: [PATCH 041/947] added CUST_ATTR_GROUP variable --- pype/modules/ftrack/lib/avalon_sync.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 49e4c178c7..db0d2e7836 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -28,6 +28,9 @@ EntitySchemas = { "config": "avalon-core:config-1.0" } +# Group name of custom attributes +CUST_ATTR_GROUP = "pype" + # name of Custom attribute that stores mongo_id from avalon db CustAttrIdKey = "avalon_mongo_id" CustAttrAutoSync = "avalon_auto_sync" From fff3c15da0a845443e040f9eef17235ebbb0bf8e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jul 2020 17:19:48 +0200 Subject: [PATCH 042/947] formatting changes --- pype/modules/ftrack/events/event_sync_to_avalon.py | 4 +++- pype/modules/ftrack/lib/__init__.py | 4 ++-- pype/modules/ftrack/lib/avalon_sync.py | 9 +++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 739ec69522..a73c198997 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -1166,7 +1166,9 @@ class SyncToAvalonEvent(BaseEvent): self.process_session.rolback() # TODO logging # TODO report - error_msg = "Failed to store MongoID to entity's custom attribute" + error_msg = ( + "Failed to store MongoID to entity's custom attribute" + ) report_msg = ( "{}||SyncToAvalon action may solve this issue" ).format(error_msg) diff --git a/pype/modules/ftrack/lib/__init__.py b/pype/modules/ftrack/lib/__init__.py index df546ab725..d8e9c7a11c 100644 --- a/pype/modules/ftrack/lib/__init__.py +++ b/pype/modules/ftrack/lib/__init__.py @@ -5,7 +5,7 @@ from .ftrack_event_handler import BaseEvent from .ftrack_action_handler import BaseAction, statics_icon from .ftrack_app_handler import AppAction -__all__ = [ +__all__ = ( "avalon_sync", "credentials", "BaseHandler", @@ -13,4 +13,4 @@ __all__ = [ "BaseAction", "statics_icon", "AppAction" -] +) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index db0d2e7836..b3e04efdc3 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -62,10 +62,11 @@ def check_regex(name, entity_type, in_schema=None, schema_patterns=None): if not schema_obj: name_pattern = default_pattern else: - name_pattern = schema_obj.get( - "properties", {}).get( - "name", {}).get( - "pattern", default_pattern + name_pattern = ( + schema_obj + .get("properties", {}) + .get("name", {}) + .get("pattern", default_pattern) ) if schema_patterns is not None: schema_patterns[schema_name] = name_pattern From 8edba507b3f1abd1b17acc554e2a33bff7e7d67a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jul 2020 17:20:53 +0200 Subject: [PATCH 043/947] renamed custom attributes contatnts and get_avalon_attrs to get_pype_attrs --- .../action_clean_hierarchical_attributes.py | 4 +- .../modules/ftrack/actions/action_delivery.py | 4 +- .../ftrack/actions/action_prepare_project.py | 4 +- .../action_store_thumbnails_to_avalon.py | 4 +- .../events/event_del_avalon_id_from_new.py | 8 ++-- .../ftrack/events/event_sync_to_avalon.py | 30 ++++++------ .../ftrack/events/event_user_assigment.py | 4 +- pype/modules/ftrack/lib/avalon_sync.py | 48 ++++++++++--------- 8 files changed, 54 insertions(+), 52 deletions(-) diff --git a/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py b/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py index 86503ff5bc..e81e587f0a 100644 --- a/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py +++ b/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py @@ -1,7 +1,7 @@ import collections import ftrack_api from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import get_avalon_attr +from pype.modules.ftrack.lib.avalon_sync import get_pype_attr class CleanHierarchicalAttrsAction(BaseAction): @@ -48,7 +48,7 @@ class CleanHierarchicalAttrsAction(BaseAction): ) entity_ids_joined = ", ".join(all_entities_ids) - attrs, hier_attrs = get_avalon_attr(session) + attrs, hier_attrs = get_pype_attr(session) for attr in hier_attrs: configuration_key = attr["key"] diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index a2048222e5..d4b86d1278 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -11,7 +11,7 @@ from avalon.vendor import filelink from pype.api import Anatomy from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey +from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from pype.modules.ftrack.lib.io_nonsingleton import DbConnector @@ -243,7 +243,7 @@ class Delivery(BaseAction): version = entity["version"] parent = asset["parent"] - parent_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) + parent_mongo_id = parent["custom_attributes"].get(CUST_ATTR_ID_KEY) if parent_mongo_id: parent_mongo_id = ObjectId(parent_mongo_id) else: diff --git a/pype/modules/ftrack/actions/action_prepare_project.py b/pype/modules/ftrack/actions/action_prepare_project.py index f51a9eb9a6..b3a2a20151 100644 --- a/pype/modules/ftrack/actions/action_prepare_project.py +++ b/pype/modules/ftrack/actions/action_prepare_project.py @@ -3,7 +3,7 @@ import json from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.api import config, Anatomy, project_overrides_dir_path -from pype.modules.ftrack.lib.avalon_sync import get_avalon_attr +from pype.modules.ftrack.lib.avalon_sync import get_pype_attr class PrepareProject(BaseAction): @@ -221,7 +221,7 @@ class PrepareProject(BaseAction): def _attributes_to_set(self, project_defaults): attributes_to_set = {} - cust_attrs, hier_cust_attrs = get_avalon_attr(self.session, True) + cust_attrs, hier_cust_attrs = get_pype_attr(self.session, True) for attr in hier_cust_attrs: key = attr["key"] diff --git a/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py b/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py index b399dab7ce..94ca503233 100644 --- a/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py +++ b/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py @@ -8,7 +8,7 @@ from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.api import Anatomy from pype.modules.ftrack.lib.io_nonsingleton import DbConnector -from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey +from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY class StoreThumbnailsToAvalon(BaseAction): @@ -390,7 +390,7 @@ class StoreThumbnailsToAvalon(BaseAction): return output asset_ent = None - asset_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) + asset_mongo_id = parent["custom_attributes"].get(CUST_ATTR_ID_KEY) if asset_mongo_id: try: asset_mongo_id = ObjectId(asset_mongo_id) diff --git a/pype/modules/ftrack/events/event_del_avalon_id_from_new.py b/pype/modules/ftrack/events/event_del_avalon_id_from_new.py index 89bad52f29..47a87e9ba5 100644 --- a/pype/modules/ftrack/events/event_del_avalon_id_from_new.py +++ b/pype/modules/ftrack/events/event_del_avalon_id_from_new.py @@ -1,5 +1,5 @@ from pype.modules.ftrack.lib import BaseEvent -from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey +from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from pype.modules.ftrack.events.event_sync_to_avalon import SyncToAvalonEvent @@ -29,7 +29,7 @@ class DelAvalonIdFromNew(BaseEvent): elif ( entity.get('action', None) == 'update' and - CustAttrIdKey in entity['keys'] and + CUST_ATTR_ID_KEY in entity['keys'] and entity_id in created ): ftrack_entity = session.get( @@ -38,11 +38,11 @@ class DelAvalonIdFromNew(BaseEvent): ) cust_attr = ftrack_entity['custom_attributes'][ - CustAttrIdKey + CUST_ATTR_ID_KEY ] if cust_attr != '': - ftrack_entity['custom_attributes'][CustAttrIdKey] = '' + ftrack_entity['custom_attributes'][CUST_ATTR_ID_KEY] = '' session.commit() except Exception: diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index a73c198997..efcb74a608 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -14,7 +14,7 @@ from avalon import schema from pype.modules.ftrack.lib import avalon_sync from pype.modules.ftrack.lib.avalon_sync import ( - CustAttrIdKey, CustAttrAutoSync, EntitySchemas + CUST_ATTR_ID_KEY, CUST_ATTR_AUTO_SYNC, EntitySchemas ) import ftrack_api from pype.modules.ftrack import BaseEvent @@ -103,7 +103,7 @@ class SyncToAvalonEvent(BaseEvent): @property def avalon_cust_attrs(self): if self._avalon_cust_attrs is None: - self._avalon_cust_attrs = avalon_sync.get_avalon_attr( + self._avalon_cust_attrs = avalon_sync.get_pype_attr( self.process_session ) return self._avalon_cust_attrs @@ -220,7 +220,7 @@ class SyncToAvalonEvent(BaseEvent): def avalon_custom_attributes(self): """Return info about changeability of entity and it's parents.""" if self._avalon_custom_attributes is None: - self._avalon_custom_attributes = avalon_sync.get_avalon_attr( + self._avalon_custom_attributes = avalon_sync.get_pype_attr( self.process_session ) return self._avalon_custom_attributes @@ -557,10 +557,10 @@ class SyncToAvalonEvent(BaseEvent): continue changes = ent_info["changes"] - if CustAttrAutoSync not in changes: + if CUST_ATTR_AUTO_SYNC not in changes: continue - auto_sync = changes[CustAttrAutoSync]["new"] + auto_sync = changes[CUST_ATTR_AUTO_SYNC]["new"] if auto_sync == "1": # Trigger sync to avalon action if auto sync was turned on ft_project = self.cur_project @@ -593,16 +593,16 @@ class SyncToAvalonEvent(BaseEvent): ft_project = self.cur_project # Check if auto-sync custom attribute exists - if CustAttrAutoSync not in ft_project["custom_attributes"]: + if CUST_ATTR_AUTO_SYNC not in ft_project["custom_attributes"]: # TODO should we sent message to someone? self.log.error(( "Custom attribute \"{}\" is not created or user \"{}\" used" " for Event server don't have permissions to access it!" - ).format(CustAttrAutoSync, self.session.api_user)) + ).format(CUST_ATTR_AUTO_SYNC, self.session.api_user)) return True # Skip if auto-sync is not set - auto_sync = ft_project["custom_attributes"][CustAttrAutoSync] + auto_sync = ft_project["custom_attributes"][CUST_ATTR_AUTO_SYNC] if auto_sync is not True: return True @@ -844,7 +844,7 @@ class SyncToAvalonEvent(BaseEvent): new_entity["custom_attributes"][key] = val - new_entity["custom_attributes"][CustAttrIdKey] = ( + new_entity["custom_attributes"][CUST_ATTR_ID_KEY] = ( str(avalon_entity["_id"]) ) ent_path = self.get_ent_path(new_entity_id) @@ -1097,7 +1097,7 @@ class SyncToAvalonEvent(BaseEvent): continue final_entity["data"][key] = val - _mongo_id_str = cust_attrs.get(CustAttrIdKey) + _mongo_id_str = cust_attrs.get(CUST_ATTR_ID_KEY) if _mongo_id_str: try: _mongo_id = ObjectId(_mongo_id_str) @@ -1158,8 +1158,8 @@ class SyncToAvalonEvent(BaseEvent): self.log.debug("Entity was synchronized <{}>".format(ent_path)) mongo_id_str = str(mongo_id) - if mongo_id_str != ftrack_ent["custom_attributes"][CustAttrIdKey]: - ftrack_ent["custom_attributes"][CustAttrIdKey] = mongo_id_str + if mongo_id_str != ftrack_ent["custom_attributes"][CUST_ATTR_ID_KEY]: + ftrack_ent["custom_attributes"][CUST_ATTR_ID_KEY] = mongo_id_str try: self.process_session.commit() except Exception: @@ -1247,7 +1247,7 @@ class SyncToAvalonEvent(BaseEvent): self.process_session, entity, hier_keys, defaults ) for key, val in hier_values.items(): - if key == CustAttrIdKey: + if key == CUST_ATTR_ID_KEY: continue output[key] = val @@ -1689,7 +1689,7 @@ class SyncToAvalonEvent(BaseEvent): if "_hierarchical" not in temp_dict: hier_mongo_id_configuration_id = None for attr in hier_attrs: - if attr["key"] == CustAttrIdKey: + if attr["key"] == CUST_ATTR_ID_KEY: hier_mongo_id_configuration_id = attr["id"] break temp_dict["_hierarchical"] = hier_mongo_id_configuration_id @@ -1706,7 +1706,7 @@ class SyncToAvalonEvent(BaseEvent): for attr in cust_attrs: key = attr["key"] - if key != CustAttrIdKey: + if key != CUST_ATTR_ID_KEY: continue if attr["entity_type"] != ent_info["entityType"]: diff --git a/pype/modules/ftrack/events/event_user_assigment.py b/pype/modules/ftrack/events/event_user_assigment.py index e198ced618..d1b3439c8f 100644 --- a/pype/modules/ftrack/events/event_user_assigment.py +++ b/pype/modules/ftrack/events/event_user_assigment.py @@ -3,7 +3,7 @@ import re import subprocess from pype.modules.ftrack import BaseEvent -from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey +from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from pype.modules.ftrack.lib.io_nonsingleton import DbConnector from bson.objectid import ObjectId @@ -106,7 +106,7 @@ class UserAssigmentEvent(BaseEvent): self.db_con.Session['AVALON_PROJECT'] = task['project']['full_name'] avalon_entity = None - parent_id = parent['custom_attributes'].get(CustAttrIdKey) + parent_id = parent['custom_attributes'].get(CUST_ATTR_ID_KEY) if parent_id: parent_id = ObjectId(parent_id) avalon_entity = self.db_con.find_one({ diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index b3e04efdc3..07a1b17770 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -32,8 +32,10 @@ EntitySchemas = { CUST_ATTR_GROUP = "pype" # name of Custom attribute that stores mongo_id from avalon db -CustAttrIdKey = "avalon_mongo_id" -CustAttrAutoSync = "avalon_auto_sync" +CUST_ATTR_ID_KEY = "avalon_mongo_id" +CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" + + def default_custom_attributes_definition(): json_file_path = os.path.join( os.path.dirname(__file__), "custom_attributes.json" @@ -76,14 +78,14 @@ def check_regex(name, entity_type, in_schema=None, schema_patterns=None): return False -def get_avalon_attr(session, split_hierarchical=True): +def get_pype_attr(session, split_hierarchical=True): custom_attributes = [] hier_custom_attributes = [] cust_attrs_query = ( "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" - " where group.name = \"avalon\"" - ) + " where group.name = \"{}\"" + ).format(CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: if split_hierarchical and cust_attr["is_hierarchical"]: @@ -334,12 +336,12 @@ class SyncEntitiesFactory: "*** Synchronization initialization started <{}>." ).format(project_full_name)) # Check if `avalon_mongo_id` custom attribute exist or is accessible - if CustAttrIdKey not in ft_project["custom_attributes"]: + if CUST_ATTR_ID_KEY not in ft_project["custom_attributes"]: items = [] items.append({ "type": "label", "value": "# Can't access Custom attribute <{}>".format( - CustAttrIdKey + CUST_ATTR_ID_KEY ) }) items.append({ @@ -699,7 +701,7 @@ class SyncEntitiesFactory: def set_cutom_attributes(self): self.log.debug("* Preparing custom attributes") # Get custom attributes and values - custom_attrs, hier_attrs = get_avalon_attr(self.session) + custom_attrs, hier_attrs = get_pype_attr(self.session) ent_types = self.session.query("select id, name from ObjectType").all() ent_types_by_name = { ent_type["name"]: ent_type["id"] for ent_type in ent_types @@ -916,7 +918,7 @@ class SyncEntitiesFactory: project_values[key] = value for key in avalon_hier: - if key == CustAttrIdKey: + if key == CUST_ATTR_ID_KEY: continue value = self.entities_dict[top_id]["avalon_attrs"][key] if value is not None: @@ -1070,7 +1072,7 @@ class SyncEntitiesFactory: same_mongo_id = [] all_mongo_ids = {} for ftrack_id, entity_dict in self.entities_dict.items(): - mongo_id = entity_dict["avalon_attrs"].get(CustAttrIdKey) + mongo_id = entity_dict["avalon_attrs"].get(CUST_ATTR_ID_KEY) if not mongo_id: continue if mongo_id in all_mongo_ids: @@ -1101,7 +1103,7 @@ class SyncEntitiesFactory: entity_dict = self.entities_dict[ftrack_id] ent_path = self.get_ent_path(ftrack_id) - mongo_id = entity_dict["avalon_attrs"].get(CustAttrIdKey) + mongo_id = entity_dict["avalon_attrs"].get(CUST_ATTR_ID_KEY) av_ent_by_mongo_id = self.avalon_ents_by_id.get(mongo_id) if av_ent_by_mongo_id: av_ent_ftrack_id = av_ent_by_mongo_id.get("data", {}).get( @@ -1122,7 +1124,7 @@ class SyncEntitiesFactory: continue _entity_dict = self.entities_dict[_ftrack_id] - _mongo_id = _entity_dict["avalon_attrs"][CustAttrIdKey] + _mongo_id = _entity_dict["avalon_attrs"][CUST_ATTR_ID_KEY] _av_ent_by_mongo_id = self.avalon_ents_by_id.get( _mongo_id ) @@ -1515,11 +1517,11 @@ class SyncEntitiesFactory: avalon_attrs = self.entities_dict[ftrack_id]["avalon_attrs"] if ( - CustAttrIdKey not in avalon_attrs or - avalon_attrs[CustAttrIdKey] != avalon_id + CUST_ATTR_ID_KEY not in avalon_attrs or + avalon_attrs[CUST_ATTR_ID_KEY] != avalon_id ): configuration_id = self.entities_dict[ftrack_id][ - "avalon_attrs_id"][CustAttrIdKey] + "avalon_attrs_id"][CUST_ATTR_ID_KEY] _entity_key = collections.OrderedDict({ "configuration_id": configuration_id, @@ -1599,7 +1601,7 @@ class SyncEntitiesFactory: # avalon_archived_by_id avalon_archived_by_name current_id = ( - entity_dict["avalon_attrs"].get(CustAttrIdKey) or "" + entity_dict["avalon_attrs"].get(CUST_ATTR_ID_KEY) or "" ).strip() mongo_id = current_id name = entity_dict["name"] @@ -1635,14 +1637,14 @@ class SyncEntitiesFactory: if current_id != new_id_str: # store mongo id to ftrack entity configuration_id = self.hier_cust_attr_ids_by_key.get( - CustAttrIdKey + CUST_ATTR_ID_KEY ) if not configuration_id: - # NOTE this is for cases when CustAttrIdKey key is not + # NOTE this is for cases when CUST_ATTR_ID_KEY key is not # hierarchical custom attribute but per entity type configuration_id = self.entities_dict[ftrack_id][ "avalon_attrs_id" - ][CustAttrIdKey] + ][CUST_ATTR_ID_KEY] _entity_key = collections.OrderedDict({ "configuration_id": configuration_id, @@ -1751,7 +1753,7 @@ class SyncEntitiesFactory: project_item = self.entities_dict[self.ft_project_id]["final_entity"] mongo_id = ( self.entities_dict[self.ft_project_id]["avalon_attrs"].get( - CustAttrIdKey + CUST_ATTR_ID_KEY ) or "" ).strip() @@ -1782,7 +1784,7 @@ class SyncEntitiesFactory: # store mongo id to ftrack entity entity = self.entities_dict[self.ft_project_id]["entity"] - entity["custom_attributes"][CustAttrIdKey] = str(new_id) + entity["custom_attributes"][CUST_ATTR_ID_KEY] = str(new_id) def _bubble_changeability(self, unchangeable_ids): unchangeable_queue = queue.Queue() @@ -2163,7 +2165,7 @@ class SyncEntitiesFactory: if new_entity_id not in p_chilren: self.entities_dict[parent_id]["children"].append(new_entity_id) - cust_attr, hier_attrs = get_avalon_attr(self.session) + cust_attr, hier_attrs = get_pype_attr(self.session) for _attr in cust_attr: key = _attr["key"] if key not in av_entity["data"]: @@ -2179,7 +2181,7 @@ class SyncEntitiesFactory: new_entity["custom_attributes"][key] = value av_entity_id = str(av_entity["_id"]) - new_entity["custom_attributes"][CustAttrIdKey] = av_entity_id + new_entity["custom_attributes"][CUST_ATTR_ID_KEY] = av_entity_id self.ftrack_avalon_mapper[new_entity_id] = av_entity_id self.avalon_ftrack_mapper[av_entity_id] = new_entity_id From e756dd60ec85f2bc8aff620349be28335c38842f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jul 2020 17:21:55 +0200 Subject: [PATCH 044/947] intent and tools attributes are based on global presets --- .../actions/action_create_cust_attrs.py | 55 +++++++++++++++++++ pype/tools/pyblish_pype/model.py | 5 +- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index cbc551ac88..a81f42f74c 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -395,6 +395,61 @@ class CustomAttributes(BaseAction): } self.process_attr_data(applications_custom_attr_data, event) + def tools_attribute(self, event): + tool_usages = self.presets.get("global", {}).get("tools") or {} + tools_data = [] + for tool_name, usage in tool_usages.items(): + if usage: + tools_data.append({tool_name: tool_name}) + + tools_custom_attr_data = { + "label": "Tools", + "key": "tools_env", + "type": "enumerator", + "is_hierarchical": True, + "group": CUST_ATTR_GROUP, + "config": { + "multiselect": True, + "data": tools_data + } + } + self.process_attr_data(tools_custom_attr_data, event) + + def intent_attribute(self, event): + intent_key_values = ( + self.presets + .get("global", {}) + .get("intent", {}) + .get("items", {}) + ) or {} + + intent_values = [] + for key, label in intent_key_values.items(): + if not key or not label: + self.log.info(( + "Skipping intent row: {{\"{}\": \"{}\"}}" + " because of empty key or label." + ).format(key, label)) + continue + + intent_values.append({key: label}) + + if not intent_values: + return + + intent_custom_attr_data = { + "label": "Intent", + "key": "intent", + "type": "enumerator", + "entity_type": "assetversion", + "group": CUST_ATTR_GROUP, + "config": { + "multiselect": False, + "data": intent_values + } + } + self.process_attr_data(intent_custom_attr_data, event) + def custom_attributes_from_file(self, event): presets = config.get_presets()["ftrack"]["ftrack_custom_attributes"] for cust_attr_data in presets: diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index 9086003258..b1f953bbe4 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -105,11 +105,10 @@ class IntentModel(QtGui.QStandardItemModel): intents_preset = ( config.get_presets() - .get("tools", {}) - .get("pyblish", {}) - .get("ui", {}) + .get("global", {}) .get("intents", {}) ) + default = intents_preset.get("default") items = intents_preset.get("items", {}) if not items: From 6e6b38e4b19b493e1830a2ff653d4cb033b4f761 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jul 2020 17:22:50 +0200 Subject: [PATCH 045/947] few fixes and only roles and default values can be set via presets --- .../actions/action_create_cust_attrs.py | 191 ++++++++++-------- 1 file changed, 106 insertions(+), 85 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index a81f42f74c..f3bba1a9b4 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -5,7 +5,9 @@ import json import arrow import ftrack_api from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey +from pype.modules.ftrack.lib.avalon_sync import ( + CUST_ATTR_ID_KEY, CUST_ATTR_GROUP, default_custom_attributes_definition +) from pype.api import config """ @@ -117,11 +119,15 @@ class CustomAttributes(BaseAction): role_list = ['Pypeclub', 'Administrator'] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") - required_keys = ['key', 'label', 'type'] - type_posibilities = [ - 'text', 'boolean', 'date', 'enumerator', - 'dynamic enumerator', 'number' - ] + required_keys = ("key", "label", "type") + + presetable_keys = ("default", "write_security_role", "read_security_role") + hierarchical_key = "is_hierarchical" + + type_posibilities = ( + "text", "boolean", "date", "enumerator", + "dynamic enumerator", "number" + ) def discover(self, session, entities, event): ''' @@ -143,22 +149,24 @@ class CustomAttributes(BaseAction): }) }) session.commit() + try: self.prepare_global_data(session) self.avalon_mongo_id_attributes(session, event) self.applications_attribute(event) + self.tools_attribute(event) + self.intent_attribute(event) self.custom_attributes_from_file(event) job['status'] = 'done' session.commit() - except Exception as exc: + except Exception: session.rollback() - job['status'] = 'failed' + job["status"] = "failed" session.commit() self.log.error( - 'Creating custom attributes failed ({})'.format(exc), - exc_info=True + "Creating custom attributes failed ({})", exc_info=True ) return True @@ -185,20 +193,17 @@ class CustomAttributes(BaseAction): self.groups = {} + self.presets = config.get_presets() + self.attrs_presets = ( + self.presets.get("ftrack", {}).get("ftrack_custom_attributes") + ) or {} + def avalon_mongo_id_attributes(self, session, event): + self.create_hierarchical_mongo_attr(session, event) + hierarchical_attr, object_type_attrs = ( self.mongo_id_custom_attributes(session) ) - - if hierarchical_attr is None: - self.create_hierarchical_mongo_attr(session) - hierarchical_attr, object_type_attrs = ( - self.mongo_id_custom_attributes(session) - ) - - if hierarchical_attr is None: - return - if object_type_attrs: self.convert_mongo_id_to_hierarchical( hierarchical_attr, object_type_attrs, session, event @@ -209,7 +214,7 @@ class CustomAttributes(BaseAction): "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" " where key = \"{}\"" - ).format(CustAttrIdKey) + ).format(CUST_ATTR_ID_KEY) mongo_id_avalon_attr = session.query(cust_attrs_query).all() heirarchical_attr = None @@ -223,32 +228,22 @@ class CustomAttributes(BaseAction): return heirarchical_attr, object_type_attrs - def create_hierarchical_mongo_attr(self, session): - # Attribute Name and Label - cust_attr_label = "Avalon/Mongo ID" - + def create_hierarchical_mongo_attr(self, session, event): # Set security roles for attribute - role_list = ("API", "Administrator", "Pypeclub") - roles = self.get_security_roles(role_list) - # Set Text type of Attribute - custom_attribute_type = self.types_per_name["text"] - # Set group to 'avalon' - group = self.get_group("avalon") - + default_role_list = ("API", "Administrator", "Pypeclub") data = { - "key": CustAttrIdKey, - "label": cust_attr_label, - "type": custom_attribute_type, + "key": CUST_ATTR_ID_KEY, + "label": "Avalon/Mongo ID", + "type": "text", "default": "", - "write_security_roles": roles, - "read_security_roles": roles, - "group": group, + "write_security_roles": default_role_list, + "read_security_roles": default_role_list, + "group": CUST_ATTR_GROUP, "is_hierarchical": True, - "entity_type": "show", - "config": json.dumps({"markdown": False}) + "config": {"markdown": False} } - self.process_attribute(data) + self.process_attr_data(data, event) def convert_mongo_id_to_hierarchical( self, hierarchical_attr, object_type_attrs, session, event @@ -339,11 +334,7 @@ class CustomAttributes(BaseAction): ) def application_definitions(self): - app_usages = ( - config.get_presets() - .get("global", {}) - .get("applications") - ) or {} + app_usages = self.presets.get("global", {}).get("applications") or {} app_definitions = [] launchers_path = os.path.join(os.environ["PYPE_CONFIG"], "launchers") @@ -387,7 +378,7 @@ class CustomAttributes(BaseAction): "key": "applications", "type": "enumerator", "entity_type": "show", - "group": "avalon", + "group": CUST_ATTR_GROUP, "config": { "multiselect": True, "data": self.application_definitions() @@ -451,17 +442,43 @@ class CustomAttributes(BaseAction): self.process_attr_data(intent_custom_attr_data, event) def custom_attributes_from_file(self, event): - presets = config.get_presets()["ftrack"]["ftrack_custom_attributes"] - for cust_attr_data in presets: - key = applications.get("key") - if key != "applications": - self.process_attr_data(cust_attr_data, event) + # Load json with custom attributes configurations + cust_attr_def = default_custom_attributes_definition() + attrs_data = [] + + # Prepare data of hierarchical attributes + hierarchical_attrs = cust_attr_def.pop(self.hierarchical_key, {}) + for key, cust_attr_data in hierarchical_attrs.items(): + cust_attr_data["key"] = key + cust_attr_data["is_hierarchical"] = True + attrs_data.append(cust_attr_data) + + # Prepare data of entity specific attributes + for entity_type, cust_attr_datas in cust_attr_def.items(): + for key, cust_attr_data in cust_attr_datas.items(): + cust_attr_data["key"] = key + cust_attr_data["entity_type"] = entity_type + attrs_data.append(cust_attr_data) + + # Process prepared data + for cust_attr_data in attrs_data: + # Add group + cust_attr_data["group"] = CUST_ATTR_GROUP + self.process_attr_data(cust_attr_data, event) def process_attr_data(self, cust_attr_data, event): - cust_attr_name = cust_attr_data.get( - "label", - cust_attr_data.get("key") - ) + attr_key = cust_attr_data["key"] + if cust_attr_data.get("is_hierarchical"): + entity_key = self.hierarchical_key + else: + entity_key = cust_attr_data["entity_type"] + + entity_presets = self.attrs_presets.get(entity_key) or {} + key_presets = entity_presets.get(attr_key) or {} + + for key, value in key_presets.items(): + if key in self.presetable_keys and value: + cust_attr_data[key] = value try: data = {} @@ -475,6 +492,8 @@ class CustomAttributes(BaseAction): self.process_attribute(data) except CustAttrException as cae: + cust_attr_name = cust_attr_data.get("label", attr_key) + if cust_attr_name: msg = 'Custom attribute error "{}" - {}'.format( cust_attr_name, str(cae) @@ -485,59 +504,61 @@ class CustomAttributes(BaseAction): self.show_message(event, msg) def process_attribute(self, data): - existing_atr = self.session.query('CustomAttributeConfiguration').all() + existing_attrs = self.session.query( + "CustomAttributeConfiguration" + ).all() matching = [] - for attr in existing_atr: + for attr in existing_attrs: if ( - attr['key'] != data['key'] or - attr['type']['name'] != data['type']['name'] + attr["key"] != data["key"] or + attr["type"]["name"] != data["type"]["name"] ): continue - if data.get('is_hierarchical', False) is True: - if attr['is_hierarchical'] is True: + if data.get("is_hierarchical") is True: + if attr["is_hierarchical"] is True: matching.append(attr) - elif 'object_type_id' in data: + elif "object_type_id" in data: if ( - attr['entity_type'] == data['entity_type'] and - attr['object_type_id'] == data['object_type_id'] + attr["entity_type"] == data["entity_type"] and + attr["object_type_id"] == data["object_type_id"] ): matching.append(attr) else: - if attr['entity_type'] == data['entity_type']: + if attr["entity_type"] == data["entity_type"]: matching.append(attr) if len(matching) == 0: - self.session.create('CustomAttributeConfiguration', data) + self.session.create("CustomAttributeConfiguration", data) self.session.commit() self.log.debug( - '{}: "{}" created'.format(self.label, data['label']) + "Custom attribute \"{}\" created".format(data["label"]) ) elif len(matching) == 1: attr_update = matching[0] for key in data: - if ( - key not in [ - 'is_hierarchical', 'entity_type', 'object_type_id' - ] + if key not in ( + "is_hierarchical", "entity_type", "object_type_id" ): attr_update[key] = data[key] - self.log.debug( - '{}: "{}" updated'.format(self.label, data['label']) - ) self.session.commit() + self.log.debug( + "Custom attribute \"{}\" updated".format(data["label"]) + ) else: - raise CustAttrException('Is duplicated') + raise CustAttrException(( + "Custom attribute is duplicated. Key: \"{}\" Type: \"{}\"" + ).format(data["key"], data["type"]["name"])) def get_required(self, attr): output = {} for key in self.required_keys: if key not in attr: raise CustAttrException( - 'Key {} is required - please set'.format(key) + "BUG: Key \"{}\" is required".format(key) ) if attr['type'].lower() not in self.type_posibilities: @@ -711,17 +732,17 @@ class CustomAttributes(BaseAction): def get_optional(self, attr): output = {} - if 'group' in attr: - output['group'] = self.get_group(attr) - if 'default' in attr: - output['default'] = self.get_default(attr) + if "group" in attr: + output["group"] = self.get_group(attr) + if "default" in attr: + output["default"] = self.get_default(attr) roles_read = [] roles_write = [] - if 'read_security_roles' in output: - roles_read = attr['read_security_roles'] - if 'read_security_roles' in output: - roles_write = attr['write_security_roles'] + if "read_security_roles" in attr: + roles_read = attr["read_security_roles"] + if "write_security_roles" in attr: + roles_write = attr["write_security_roles"] output['read_security_roles'] = self.get_security_roles(roles_read) output['write_security_roles'] = self.get_security_roles(roles_write) From 76fe88fbccab959ff8c333dcb88e06e950919a5c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jul 2020 10:19:58 +0200 Subject: [PATCH 046/947] feat(resolve): config moved to plugins and widget rename --- pype/hosts/resolve/plugin.py | 4 +-- .../resolve/create/create_shot_clip.py | 32 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 4e7ac80add..29e544cb47 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -7,7 +7,7 @@ from pype.api import config from Qt import QtWidgets, QtCore -class Universal_widget(QtWidgets.QDialog): +class Creator_widget(QtWidgets.QDialog): # output items items = dict() @@ -270,4 +270,4 @@ class Creator(api.Creator): else: self.selected = resolve.get_current_track_items(filter=False) - self.widget = Universal_widget + self.widget = Creator_widget diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index 43207743e2..e4b3e8fe2a 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -13,17 +13,41 @@ class CreateShotClip(resolve.Creator): gui_name = "Pype sequencial rename with hirerarchy" gui_info = "Define sequencial rename and fill hierarchy data." + gui_inputs = { + "clipName": "{episode}{sequence}{shot}", + "hierarchy": "{folder}/{sequence}/{shot}", + "countFrom": 10, + "steps": 10, + "hierarchyData": { + "folder": "shots", + "shot": "sh####", + "track": "{track}", + "sequence": "sc010", + "episode": "ep01" + } + } presets = None def process(self): - print(f"__ selected_clips: {self.selected}") + # solve gui inputs overwrites from presets + # overwrite gui inputs from presets + for k, v in self.gui_inputs.items(): + if isinstance(v, dict): + # nested dictionary (only one level allowed) + for _k, _v in v.items(): + if self.presets.get(_k): + self.gui_inputs[k][_k] = self.presets[_k] + if self.presets.get(k): + self.gui_inputs[k] = self.presets[k] + # open widget for plugins inputs + widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs) + widget.exec_() + + print(f"__ selected_clips: {self.selected}") if len(self.selected) < 1: return - widget = self.widget(self.gui_name, self.gui_info, self.presets) - widget.exec_() - if not widget.result: print("Operation aborted") return From 309515461aeee31f8d2df4276f7b5942fe240627 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jul 2020 10:31:11 +0200 Subject: [PATCH 047/947] feat(resolve): hound fixes --- pype/hosts/resolve/plugin.py | 4 ++-- pype/plugins/resolve/publish/collect_clips.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 29e544cb47..67f2820990 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -7,7 +7,7 @@ from pype.api import config from Qt import QtWidgets, QtCore -class Creator_widget(QtWidgets.QDialog): +class CreatorWidget(QtWidgets.QDialog): # output items items = dict() @@ -270,4 +270,4 @@ class Creator(api.Creator): else: self.selected = resolve.get_current_track_items(filter=False) - self.widget = Creator_widget + self.widget = CreatorWidget diff --git a/pype/plugins/resolve/publish/collect_clips.py b/pype/plugins/resolve/publish/collect_clips.py index 0f02f26f2e..f86e5c8384 100644 --- a/pype/plugins/resolve/publish/collect_clips.py +++ b/pype/plugins/resolve/publish/collect_clips.py @@ -3,6 +3,7 @@ from pyblish import api from pype.hosts import resolve import json + class CollectClips(api.ContextPlugin): """Collect all Track items selection.""" @@ -156,4 +157,6 @@ class CollectClips(api.ContextPlugin): "_clipIn": clip_in, "_clipOut": clip_out } - self.log.info("context.data[\"assetsShared\"]: {}".format(context.data["assetsShared"])) + self.log.info( + "context.data[\"assetsShared\"]: {}".format( + context.data["assetsShared"])) From e259a55af9e1efe648842cc3a94bfca6d34c0f62 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 13 Jul 2020 10:42:26 +0200 Subject: [PATCH 048/947] feat(resolve): hound fixes --- pype/hosts/resolve/plugin.py | 6 +++--- pype/hosts/resolve/utility_scripts/test.py | 4 +++- pype/plugins/resolve/create/create_shot_clip.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pype/hosts/resolve/plugin.py b/pype/hosts/resolve/plugin.py index 67f2820990..72eec04896 100644 --- a/pype/hosts/resolve/plugin.py +++ b/pype/hosts/resolve/plugin.py @@ -13,7 +13,7 @@ class CreatorWidget(QtWidgets.QDialog): items = dict() def __init__(self, name, info, presets, parent=None): - super(Universal_widget, self).__init__(parent) + super(CreatorWidget, self).__init__(parent) self.setObjectName(name) @@ -86,11 +86,11 @@ class CreatorWidget(QtWidgets.QDialog): data[k] = self.value(v) elif getattr(v, "value", None): print(f"normal int: {k}") - result = getattr(v, "value") + result = v.value() data[k] = result() else: print(f"normal text: {k}") - result = getattr(v, "text") + result = v.text() data[k] = result() return data diff --git a/pype/hosts/resolve/utility_scripts/test.py b/pype/hosts/resolve/utility_scripts/test.py index cf7db3b7e5..69dc4768bd 100644 --- a/pype/hosts/resolve/utility_scripts/test.py +++ b/pype/hosts/resolve/utility_scripts/test.py @@ -1,6 +1,8 @@ #! python3 import sys from pype.api import Logger +import DaVinciResolveScript as bmdvr + log = Logger().get_logger(__name__) @@ -10,7 +12,7 @@ def main(): bm = bmdvr.utils.get_resolve_module() log.info(f"blackmagicmodule: {bm}") -import DaVinciResolveScript as bmdvr + print(f"_>> bmdvr.scriptapp(Resolve): {bmdvr.scriptapp('Resolve')}") diff --git a/pype/plugins/resolve/create/create_shot_clip.py b/pype/plugins/resolve/create/create_shot_clip.py index e4b3e8fe2a..bd2e013fac 100644 --- a/pype/plugins/resolve/create/create_shot_clip.py +++ b/pype/plugins/resolve/create/create_shot_clip.py @@ -70,10 +70,10 @@ class CreateShotClip(resolve.Creator): t_data["clip"]["item"].ClearClipColor() # convert track item to timeline media pool item - c_clip = resolve.create_compound_clip( + resolve.create_compound_clip( t_data, mp_folder, rename=True, **dict( {"presets": widget.result}) - ) + ) From 000885da3cf6472f20b1f2297490dfd419b4b46d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 11:07:26 +0200 Subject: [PATCH 049/947] add MissingPermision to ftrack.lib --- pype/modules/ftrack/lib/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/__init__.py b/pype/modules/ftrack/lib/__init__.py index df546ab725..9de477059a 100644 --- a/pype/modules/ftrack/lib/__init__.py +++ b/pype/modules/ftrack/lib/__init__.py @@ -1,6 +1,6 @@ from . import avalon_sync from . import credentials -from .ftrack_base_handler import BaseHandler +from .ftrack_base_handler import BaseHandler, MissingPermision from .ftrack_event_handler import BaseEvent from .ftrack_action_handler import BaseAction, statics_icon from .ftrack_app_handler import AppAction @@ -9,6 +9,7 @@ __all__ = [ "avalon_sync", "credentials", "BaseHandler", + "MissingPermision", "BaseEvent", "BaseAction", "statics_icon", From 22ff14b93d7bfb62808f1b784d7c0f85bc722bd0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 11:18:28 +0200 Subject: [PATCH 050/947] set CLASS_DEFINIION in clockify to be able set workspace attribute --- pype/modules/clockify/__init__.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pype/modules/clockify/__init__.py b/pype/modules/clockify/__init__.py index aab0d048de..0ee2189fa5 100644 --- a/pype/modules/clockify/__init__.py +++ b/pype/modules/clockify/__init__.py @@ -3,12 +3,8 @@ from .widget_settings import ClockifySettings from .widget_message import MessageWidget from .clockify import ClockifyModule -__all__ = [ - "ClockifyAPI", - "ClockifySettings", - "ClockifyModule", - "MessageWidget" -] - +CLASS_DEFINIION = ClockifyModule + + def tray_init(tray_widget, main_widget): return ClockifyModule(main_widget, tray_widget) From be135a79322227c9d824017dc2a3b28f7786a48f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 11:18:58 +0200 Subject: [PATCH 051/947] added workspace_name attribute to clockify --- pype/modules/clockify/clockify.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 2ab22702c1..02b322c1c6 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -8,7 +8,14 @@ from . import ClockifySettings, ClockifyAPI, MessageWidget class ClockifyModule: + workspace_name = None + def __init__(self, main_parent=None, parent=None): + if not self.workspace_name: + raise Exception("Clockify Workspace is not set in config.") + + os.environ["CLOCKIFY_WORKSPACE"] = self.workspace_name + self.log = Logger().get_logger(self.__class__.__name__, "PypeTray") self.main_parent = main_parent From 0ace96c4dc7e8b012c5946ea25f56b3029e5d79f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 11:23:43 +0200 Subject: [PATCH 052/947] catch all exceptions in tray during module object creation --- pype/tools/tray/pype_tray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 5b1185fa71..7dda8bf4f7 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -203,7 +203,7 @@ class TrayManager: obj.set_qaction(action, self.icon_failed) self.modules[name] = obj self.log.info("{} - Module imported".format(title)) - except ImportError as ie: + except Exception as exc: if self.services_submenu is None: self.services_submenu = QtWidgets.QMenu( 'Services', self.tray_widget.menu @@ -212,7 +212,7 @@ class TrayManager: action.setIcon(self.icon_failed) self.services_submenu.addAction(action) self.log.warning( - "{} - Module import Error: {}".format(title, str(ie)), + "{} - Module import Error: {}".format(title, str(exc)), exc_info=True ) return False From 53bdf84abb9407ae5e3970a6a40b335d4111b3d5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 13:59:17 +0200 Subject: [PATCH 053/947] removed Thumbs.db from pype/res --- res/icons/Thumbs.db | Bin 6144 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 res/icons/Thumbs.db diff --git a/res/icons/Thumbs.db b/res/icons/Thumbs.db deleted file mode 100644 index fa56c871f60da645a236c2786a1fdfe9f5693dc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6144 zcmeH}dpOkDAIHy_VT^>4YbAGs$-2hOR&FiZ=!Z})CHG6Yvy7Cqp_^5|HA11<PcBN=7 z2YeLz01Flo0^s?t=NNd6vJ^r9aKQ`oei1>C<%a;k%dhbT?ZA7uRy2PoIG99k2%2*| zgcm}9@Ilb`U*rF82dqILETKRKn1BG-hJXl|?avP}cw$y|1@(i!iy!G$Ve1+@Fj&~h zP++;I7T5%Ifis}O+5^^JOZQs99cU~S9pDKlK=YHsszMi9nICOG+3}+nk~T!kyp26{^?pu%gn<<9@3hVw7!L4ELZ=NEx}DId-63Fp`ZmFmFa28-7c z+EBeFeDdi~{s;OM;cycl=113!<_*os-~Ikq!T0=-)etmwf)MyG01zR3|kk%uTi&@q%?OM)mvR3NGlGGrq}4e|{{9ijn2 zPlgsm8-niBCWtOX4?=-_^)2*Nemc+`p}Ft)>ygX<$^`DjwM{-l{YTBD{9^LRzce>B$~h{ zCN3elR#AzhtfH#3Nmq|T{dU`TCZ^wSr@#M&h}oS?qruN4jon75e zdU`p}2VV^RHvId@%dzo^$*Jj?*|~S9E)2jf%lcEK4+*C#ImoQ5-|1Eh+mvVbT9c*`LDx($x$2 zu^5;D^ynKhYohcjTa`=3c`*OtktL<8CUiOjrc7~3qrnlWri_ad78uRHtNzI$BXjrRQ z$*j3qu^j_$HUrCly-qrX8)JY*ELCPhYuyG?$(Z@&UjG^gaW4DQ&GORCszLN};39~L zQ5{><8>@-AzF$ww)^3Bxc*J45=AXTV$r~IDCBP-iSB^7x5iloUjsT|{2)L5vYN-(# zoN$%zvVE2nxzbEKC@rLSCrycAo?T|?bjEkf+T0UKv~4{Wt9ulcb4{BMHdYk$U7Ymo z#IpqF1h&CZa^Wb|-V4;XQzuWAQRO=H_dh>%D_+dHK{mwf-&K{3>pFiDWexS?BL)XB z&IdEzk=ZJ4`-&p`iUXu1ZpvKq8xF<^X&!5AUEfmi>t$k(8L7M~TE&hM$}n+Fh=~>= z{=8kkL3Ge<{AKgnmsJ)8`T@TC2Py17j=Y}qcPMDxAsC-EC;i`9!G8s3($FLTFhc+JhhL`IjMLdO_1@63qLD3G2MRF00FX%wXZr6FsYMU7$q`$ zcG_lZONVgNljhqD?e+ZpjM#JlrX>o~?l^JPyse(Bs3@*yXN-5cKRtBQjplNT^$37r zQo>o^)hS)cRNGw_RU^BH+Dui*p~ZGS(`Jp#Z(_UeJ+`+qZ`pOR;7FiS-}}(s(F~>m z(zEPkEA@*1$$aoXjnPAjp=w()z6Adh0}@~TZ^_rceE(~mw{Cdj>^kv$-fm&MpZP9e z{zBhORKP{*h|?MbXg9pCN5FKo-I&+OYX{wJy%I9(?t4au)R(O8;dgYI%iBKi=)~N~ zrX~x6>7;IUZD|DpvLb5e3xc)GsMqd+{lgLse(rG#-nuPAPClYCBTZ*4d;<>_rn39n zM{dw*eGY-|^qo`ktU_WdJGbwdKRiGlX)Aba+nv)s(3@=FGcR~qytj5Zd1~Ns_|u1@ z%!$yERx|RQNYa&;8%Ui=R}W|~2;S+M{8#Ba@Kk+YA$pi%jr(}MkX@f=Pl2Du`c4D- z8}i zEB?X$;2%C%^+Ru@$+cgCe|-IZTKO*g^8KTCe6iaien-T{AeP0Wc8#5uj-IC^?~tT| z1haxu5J0TAo7iJt$6asbSD!tuoK&G4m~wP=>5xU2AMv3Ucf<^|7lTD%hlMw6-G-q* z9_qf9?75^nI*=aB43F%Vc24>&l4dKaXqNh7XW~zcl5^(*r7I*&l-{TxXOf+5Ml~GN zs74lnX{jn%dGU_d7awG>&0d8Tj`|A*-<&qo&CZ6b7o~%EioTP5 zc~_xUFmv8I(L7(Rx2n2KZNad+Y_B@8&&WS~i8_DFpf6kFZ_LR)>kaghn=q;x0+M_2 z`?1kHfbx*m0zW?ut{|Y*_s0AigW0(2rdDUB$8&ko9kY~kNC6Id9)mgG`-c8*zP@hN z>#GE%)Hw5N`4cAO()YVZXD%JNaj+&L^SI98=Amdxc~Pph)On_+m-3S>TGvQ;KD;4$ z8)vaPuYdMz*Q&J2q0_e?Ty1)tLoAZS{TyT{|I~{qJ!|t$dSSW>0X2^iaH>EJ&*@6! zyc+6I>ph@kXgn96$yJBr><*zbs0uEL@y}ue&$%DD?PH!Ls@z1Qv6I#IDZgsl(|72& kBV1h;Z#MfU+O7Nv+GgGN`$lJ^|FXW7PpGf$7rX=i0f1|-6aWAK From 21645bb30bcd8f6c02751e24f660e73ce5c51aa7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 13:59:52 +0200 Subject: [PATCH 054/947] moved remaining resources from `~/pype/res/` to `~/pype/pype/resources/icons/` --- {res => pype/resources}/icons/folder-favorite.png | Bin {res => pype/resources}/icons/folder-favorite2.png | Bin {res => pype/resources}/icons/folder-favorite3.png | Bin {res => pype/resources}/icons/inventory.png | Bin {res => pype/resources}/icons/loader.png | Bin {res => pype/resources}/icons/lookmanager.png | Bin {res => pype/resources}/icons/workfiles.png | Bin 7 files changed, 0 insertions(+), 0 deletions(-) rename {res => pype/resources}/icons/folder-favorite.png (100%) rename {res => pype/resources}/icons/folder-favorite2.png (100%) rename {res => pype/resources}/icons/folder-favorite3.png (100%) rename {res => pype/resources}/icons/inventory.png (100%) rename {res => pype/resources}/icons/loader.png (100%) rename {res => pype/resources}/icons/lookmanager.png (100%) rename {res => pype/resources}/icons/workfiles.png (100%) diff --git a/res/icons/folder-favorite.png b/pype/resources/icons/folder-favorite.png similarity index 100% rename from res/icons/folder-favorite.png rename to pype/resources/icons/folder-favorite.png diff --git a/res/icons/folder-favorite2.png b/pype/resources/icons/folder-favorite2.png similarity index 100% rename from res/icons/folder-favorite2.png rename to pype/resources/icons/folder-favorite2.png diff --git a/res/icons/folder-favorite3.png b/pype/resources/icons/folder-favorite3.png similarity index 100% rename from res/icons/folder-favorite3.png rename to pype/resources/icons/folder-favorite3.png diff --git a/res/icons/inventory.png b/pype/resources/icons/inventory.png similarity index 100% rename from res/icons/inventory.png rename to pype/resources/icons/inventory.png diff --git a/res/icons/loader.png b/pype/resources/icons/loader.png similarity index 100% rename from res/icons/loader.png rename to pype/resources/icons/loader.png diff --git a/res/icons/lookmanager.png b/pype/resources/icons/lookmanager.png similarity index 100% rename from res/icons/lookmanager.png rename to pype/resources/icons/lookmanager.png diff --git a/res/icons/workfiles.png b/pype/resources/icons/workfiles.png similarity index 100% rename from res/icons/workfiles.png rename to pype/resources/icons/workfiles.png From 308d643bae2036d81f580900a5b30587a833568b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 14:12:18 +0200 Subject: [PATCH 055/947] moved resources changed in code --- pype/hosts/maya/customize.py | 7 ++----- pype/hosts/nuke/utils.py | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index 8bd7052d9e..cf179e1d63 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -70,16 +70,13 @@ def override_component_mask_commands(): def override_toolbox_ui(): """Add custom buttons in Toolbox as replacement for Maya web help icon.""" - import pype - res = os.path.join(os.path.dirname(os.path.dirname(pype.__file__)), - "res") - icons = os.path.join(res, "icons") - import avalon.tools.sceneinventory as inventory import avalon.tools.loader as loader from avalon.maya.pipeline import launch_workfiles_app import mayalookassigner + from pype.api import resources + icons = resources.get_resource("icons") # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" diff --git a/pype/hosts/nuke/utils.py b/pype/hosts/nuke/utils.py index aa5bc1077e..72c7b7bc14 100644 --- a/pype/hosts/nuke/utils.py +++ b/pype/hosts/nuke/utils.py @@ -1,6 +1,7 @@ import os import nuke from avalon.nuke import lib as anlib +from pype.api import resources def set_context_favorites(favorites={}): @@ -9,9 +10,7 @@ def set_context_favorites(favorites={}): Argumets: favorites (dict): couples of {name:path} """ - dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - icon_path = os.path.join(dir, 'res', 'icons', 'folder-favorite3.png') - + icon_path = resources.get_resource("icons", "folder-favorite3.png") for name, path in favorites.items(): nuke.addFavoriteDir( name, From aea8f16437803928e65012fd17d78ce936b7aa37 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 15:35:26 +0200 Subject: [PATCH 056/947] fix intent key --- pype/tools/pyblish_pype/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index b1f953bbe4..b7b300f154 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -106,7 +106,7 @@ class IntentModel(QtGui.QStandardItemModel): intents_preset = ( config.get_presets() .get("global", {}) - .get("intents", {}) + .get("intent", {}) ) default = intents_preset.get("default") From 11151b412244b7d6b6a575612d694b159f249f34 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 16:24:38 +0200 Subject: [PATCH 057/947] added possibility to add task entity type custom attributes --- .../ftrack/actions/action_create_cust_attrs.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index f3bba1a9b4..593da28cb0 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -455,10 +455,20 @@ class CustomAttributes(BaseAction): # Prepare data of entity specific attributes for entity_type, cust_attr_datas in cust_attr_def.items(): - for key, cust_attr_data in cust_attr_datas.items(): - cust_attr_data["key"] = key - cust_attr_data["entity_type"] = entity_type - attrs_data.append(cust_attr_data) + if entity_type.lower() != "task": + for key, cust_attr_data in cust_attr_datas.items(): + cust_attr_data["key"] = key + cust_attr_data["entity_type"] = entity_type + attrs_data.append(cust_attr_data) + continue + + # Task should have nested level for object type + for object_type, _cust_attr_datas in cust_attr_datas.items(): + for key, cust_attr_data in _cust_attr_datas.items(): + cust_attr_data["key"] = key + cust_attr_data["entity_type"] = entity_type + cust_attr_data["object_type"] = object_type + attrs_data.append(cust_attr_data) # Process prepared data for cust_attr_data in attrs_data: From c59c589d38e05ccd6c84a70107bead9ebb63b023 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 16:26:48 +0200 Subject: [PATCH 058/947] modified docstring for custom attributes --- .../actions/action_create_cust_attrs.py | 143 ++++++++++-------- 1 file changed, 80 insertions(+), 63 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 593da28cb0..8b23fc4749 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -12,92 +12,109 @@ from pype.api import config """ This action creates/updates custom attributes. -- first part take care about avalon_mongo_id attribute -- second part is based on json file in templates: - ~/PYPE-TEMPLATES/presets/ftrack/ftrack_custom_attributes.json - - you can add Custom attributes based on these conditions +## First part take care about special attributes + - `avalon_mongo_id` for storing Avalon MongoID + - `applications` based on applications usages + - `tools` based on tools usages + +## Second part is based on json file in ftrack module. +File location: `~/pype/pype/modules/ftrack/ftrack_custom_attributes.json` + +Data in json file is nested dictionary. Keys in first dictionary level +represents Ftrack entity type (task, show, assetversion, user, list, asset) +and dictionary value define attribute. + +There is special key for hierchical attributes `is_hierarchical`. + +Entity types `task` requires to define task object type (Folder, Shot, +Sequence, Task, Library, Milestone, Episode, Asset Build, etc.) at second +dictionary level, task's attributes are nested more. + +*** Not Changeable ********************************************************* + +group (string) + - name of group + - based on attribute `pype.modules.ftrack.lib.CUST_ATTR_GROUP` + - "pype" by default *** Required *************************************************************** label (string) - - label that will show in ftrack + - label that will show in ftrack key (string) - - must contain only chars [a-z0-9_] + - must contain only chars [a-z0-9_] type (string) - - type of custom attribute - - possibilities: text, boolean, date, enumerator, dynamic enumerator, number + - type of custom attribute + - possibilities: + text, boolean, date, enumerator, dynamic enumerator, number *** Required with conditions *********************************************** -entity_type (string) - - if 'is_hierarchical' is set to False - - type of entity - - possibilities: task, show, assetversion, user, list, asset - config (dictionary) - - for each entity type different requirements and possibilities: - - enumerator: multiSelect = True/False(default: False) - data = {key_1:value_1,key_2:value_2,..,key_n:value_n} - - 'data' is Required value with enumerator - - 'key' must contain only chars [a-z0-9_] + - for each attribute type different requirements and possibilities: + - enumerator: + multiSelect = True/False(default: False) + data = {key_1:value_1,key_2:value_2,..,key_n:value_n} + - 'data' is Required value with enumerator + - 'key' must contain only chars [a-z0-9_] - - number: isdecimal = True/False(default: False) + - number: + isdecimal = True/False(default: False) - - text: markdown = True/False(default: False) + - text: + markdown = True/False(default: False) -object_type (string) - - IF ENTITY_TYPE is set to 'task' - - default possibilities: Folder, Shot, Sequence, Task, Library, - Milestone, Episode, Asset Build,... - -*** Optional *************************************************************** +*** Presetable keys ********************************************************** write_security_roles/read_security_roles (array of strings) - - default: ["ALL"] - - strings should be role names (e.g.: ["API", "Administrator"]) - - if set to ["ALL"] - all roles will be availabled - - if first is 'except' - roles will be set to all except roles in array - - Warning: Be carefull with except - roles can be different by company - - example: - write_security_roles = ["except", "User"] - read_security_roles = ["ALL"] - - User is unable to write but can read - -group (string) - - default: None - - name of group + - default: ["ALL"] + - strings should be role names (e.g.: ["API", "Administrator"]) + - if set to ["ALL"] - all roles will be availabled + - if first is 'except' - roles will be set to all except roles in array + - Warning: Be carefull with except - roles can be different by company + - example: + write_security_roles = ["except", "User"] + read_security_roles = ["ALL"] # (User is can only read) default - - default: None - - sets default value for custom attribute: - - text -> string - - number -> integer - - enumerator -> array with string of key/s - - boolean -> bool true/false - - date -> string in format: 'YYYY.MM.DD' or 'YYYY.MM.DD HH:mm:ss' - - example: "2018.12.24" / "2018.1.1 6:0:0" - - dynamic enumerator -> DON'T HAVE DEFAULT VALUE!!! + - default: None + - sets default value for custom attribute: + - text -> string + - number -> integer + - enumerator -> array with string of key/s + - boolean -> bool true/false + - date -> string in format: 'YYYY.MM.DD' or 'YYYY.MM.DD HH:mm:ss' + - example: "2018.12.24" / "2018.1.1 6:0:0" + - dynamic enumerator -> DON'T HAVE DEFAULT VALUE!!! -is_hierarchical (bool) - - default: False - - will set hierachical attribute - - False by default - -EXAMPLE: -{ +Example: +``` +"show": { "avalon_auto_sync": { - "label": "Avalon auto-sync", - "key": "avalon_auto_sync", - "type": "boolean", - "entity_type": "show", - "group": "avalon", - "default": false, - "write_security_role": ["API","Administrator"], - "read_security_role": ["API","Administrator"] + "label": "Avalon auto-sync", + "type": "boolean", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] + } +}, +"is_hierarchical": { + "fps": { + "label": "FPS", + "type": "number", + "config": {"isdecimal": true} + } +}, +"task": { + "library": { + "my_attr_name": { + "label": "My Attr", + "type": "number" + } } } +``` """ From c76e5c9acc59147d760e5993b4eaf9b59b04361a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 16:38:11 +0200 Subject: [PATCH 059/947] task entity type can be presetable --- .../actions/action_create_cust_attrs.py | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 8b23fc4749..0c7e311377 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -211,9 +211,31 @@ class CustomAttributes(BaseAction): self.groups = {} self.presets = config.get_presets() - self.attrs_presets = ( + self.attrs_presets = self.prepare_attribute_pressets() + + def prepare_attribute_pressets(self): + output = {} + + attr_presets = ( self.presets.get("ftrack", {}).get("ftrack_custom_attributes") ) or {} + for entity_type, preset in attr_presets.items(): + # Lower entity type + entity_type = entity_type.lower() + # Just store if entity type is not "task" + if entity_type != "task": + output[entity_type] = preset + continue + + # Prepare empty dictionary for entity type if not set yet + if entity_type not in output: + output[entity_type] = {} + + # Store presets per lowered object type + for obj_type, _preset in preset.items(): + output[entity_type][obj_type.lower()] = _preset + + return output def avalon_mongo_id_attributes(self, session, event): self.create_hierarchical_mongo_attr(session, event) @@ -493,19 +515,30 @@ class CustomAttributes(BaseAction): cust_attr_data["group"] = CUST_ATTR_GROUP self.process_attr_data(cust_attr_data, event) - def process_attr_data(self, cust_attr_data, event): - attr_key = cust_attr_data["key"] - if cust_attr_data.get("is_hierarchical"): + def presets_for_attr_data(self, attr_data): + output = {} + + attr_key = attr_data["key"] + if attr_data.get("is_hierarchical"): entity_key = self.hierarchical_key else: - entity_key = cust_attr_data["entity_type"] + entity_key = attr_data["entity_type"] entity_presets = self.attrs_presets.get(entity_key) or {} + if entity_key.lower() == "task": + object_type = attr_data["object_type"] + entity_presets = entity_presets.get(object_type.lower()) or {} + key_presets = entity_presets.get(attr_key) or {} for key, value in key_presets.items(): if key in self.presetable_keys and value: - cust_attr_data[key] = value + output[key] = value + return output + + def process_attr_data(self, cust_attr_data, event): + attr_presets = self.presets_for_attr_data(cust_attr_data) + cust_attr_data.update(attr_presets) try: data = {} @@ -519,7 +552,7 @@ class CustomAttributes(BaseAction): self.process_attribute(data) except CustAttrException as cae: - cust_attr_name = cust_attr_data.get("label", attr_key) + cust_attr_name = cust_attr_data.get("label", cust_attr_data["key"]) if cust_attr_name: msg = 'Custom attribute error "{}" - {}'.format( From c136e5cb086fc7daabe77f3ab60268559a83ab78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jul 2020 17:26:15 +0200 Subject: [PATCH 060/947] hound fixes --- .../ftrack/events/event_del_avalon_id_from_new.py | 9 +++------ pype/modules/ftrack/lib/avalon_sync.py | 4 +++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/events/event_del_avalon_id_from_new.py b/pype/modules/ftrack/events/event_del_avalon_id_from_new.py index 47a87e9ba5..ee82c9589d 100644 --- a/pype/modules/ftrack/events/event_del_avalon_id_from_new.py +++ b/pype/modules/ftrack/events/event_del_avalon_id_from_new.py @@ -37,12 +37,9 @@ class DelAvalonIdFromNew(BaseEvent): entity_id ) - cust_attr = ftrack_entity['custom_attributes'][ - CUST_ATTR_ID_KEY - ] - - if cust_attr != '': - ftrack_entity['custom_attributes'][CUST_ATTR_ID_KEY] = '' + cust_attrs = ftrack_entity["custom_attributes"] + if cust_attrs[CUST_ATTR_ID_KEY]: + cust_attrs[CUST_ATTR_ID_KEY] = "" session.commit() except Exception: diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 07a1b17770..f78e617f3a 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -1124,7 +1124,9 @@ class SyncEntitiesFactory: continue _entity_dict = self.entities_dict[_ftrack_id] - _mongo_id = _entity_dict["avalon_attrs"][CUST_ATTR_ID_KEY] + _mongo_id = ( + _entity_dict["avalon_attrs"][CUST_ATTR_ID_KEY] + ) _av_ent_by_mongo_id = self.avalon_ents_by_id.get( _mongo_id ) From b8afa33abbe4b92bdabb3b0db9f26f1d0fc33608 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 10:50:19 +0200 Subject: [PATCH 061/947] fix import --- pype/modules/clockify/ftrack_actions/action_clockify_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index a041e6ada6..1fcb72d85c 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -4,7 +4,7 @@ import argparse import logging import json import ftrack_api -from pype.modules.ftrack import BaseAction, MissingPermision +from pype.modules.ftrack.lib import BaseAction, MissingPermision from pype.modules.clockify import ClockifyAPI From 09dc456f738c8ffb62fcf2f667eb3f4fa8dd99f9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:20:25 +0200 Subject: [PATCH 062/947] removed preregister method because api check must happen just before launch because is running in different process than tray --- .../clockify/ftrack_actions/action_clockify_sync.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 1fcb72d85c..bcc33ba6c1 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -26,15 +26,6 @@ class SyncClocify(BaseAction): #: CLockifyApi clockapi = ClockifyAPI() - def preregister(self): - if self.clockapi.workspace_id is None: - return "Clockify Workspace or API key are not set!" - - if self.clockapi.validate_workspace_perm() is False: - raise MissingPermision('Clockify') - - return True - def discover(self, session, entities, event): ''' Validation ''' if len(entities) != 1: From 11a680dba53a9f8b4c318146302273d893598b86 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:20:55 +0200 Subject: [PATCH 063/947] simplified discover --- .../clockify/ftrack_actions/action_clockify_sync.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index bcc33ba6c1..7956985012 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -27,13 +27,12 @@ class SyncClocify(BaseAction): clockapi = ClockifyAPI() def discover(self, session, entities, event): - ''' Validation ''' - if len(entities) != 1: - return False - - if entities[0].entity_type.lower() != "project": - return False - return True + if ( + len(entities) == 1 + and entities[0].entity_type.lower() == "project" + ): + return True + return False def launch(self, session, entities, event): # JOB SETTINGS From 5c20c38630184046e0526f34845e9f7779f5f07d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:21:20 +0200 Subject: [PATCH 064/947] api check addedto beginning of launch method --- .../clockify/ftrack_actions/action_clockify_sync.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 7956985012..a3dc9ae278 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -35,6 +35,19 @@ class SyncClocify(BaseAction): return False def launch(self, session, entities, event): + self.clockapi.set_api() + if self.clockapi.workspace_id is None: + return { + "success": False, + "message": "Clockify Workspace or API key are not set!" + } + + if self.clockapi.validate_workspace_perm() is False: + return { + "success": False, + "message": "Missing permissions for this action!" + } + # JOB SETTINGS userId = event['source']['user']['id'] user = session.query('User where id is ' + userId).one() From 88ddce4313a4e0fde8dd3c3c8e748f9db73c6262 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:21:35 +0200 Subject: [PATCH 065/947] removed deprecated parts of stync action --- .../ftrack_actions/action_clockify_sync.py | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index a3dc9ae278..335c045952 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -111,48 +111,4 @@ class SyncClocify(BaseAction): def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - - if not isinstance(session, ftrack_api.session.Session): - return - SyncClocify(session).register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session() - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) From 56736a62c1cc232858796f40686fc8daa9cd263c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:22:28 +0200 Subject: [PATCH 066/947] made ftrack part of getting data easier to read --- .../ftrack_actions/action_clockify_sync.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 335c045952..105398d58c 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -60,23 +60,25 @@ class SyncClocify(BaseAction): }) }) session.commit() + + project_entity = entities[0] + if project_entity.entity_type.lower() != "project": + project_entity = self.get_project_from_entity(project_entity) + + project_name = project_entity["full_name"] + self.log.info( + "Synchronization of project \"{}\" to clockify begins.".format( + project_name + ) + ) + task_types = ( + project_entity["project_schema"]["_task_type_schema"]["types"] + ) + task_type_names = [ + task_type["name"] for task_type in task_types + ] try: - entity = entities[0] - - if entity.entity_type.lower() == 'project': - project = entity - else: - project = entity['project'] - project_name = project['full_name'] - - task_types = [] - for task_type in project['project_schema']['_task_type_schema'][ - 'types' - ]: - task_types.append(task_type['name']) - clockify_projects = self.clockapi.get_projects() - if project_name not in clockify_projects: response = self.clockapi.add_project(project_name) if 'id' not in response: From acd933954b6edb5f10932f4178d9624c0bcafe6f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:22:52 +0200 Subject: [PATCH 067/947] do not store project_id because is not used --- pype/modules/clockify/ftrack_actions/action_clockify_sync.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 105398d58c..b490a51df6 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -89,9 +89,6 @@ class SyncClocify(BaseAction): 'success': False, 'message': 'Can\'t create project, unexpected error' } - project_id = response['id'] - else: - project_id = clockify_projects[project_name] clockify_workspace_tags = self.clockapi.get_tags() for task_type in task_types: From 8e05b9f33cf9967c9fc35706aa0ddc8547ca9f9a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:23:25 +0200 Subject: [PATCH 068/947] task types have more logs --- .../ftrack_actions/action_clockify_sync.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index b490a51df6..2047bb69bb 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -91,14 +91,21 @@ class SyncClocify(BaseAction): } clockify_workspace_tags = self.clockapi.get_tags() - for task_type in task_types: - if task_type not in clockify_workspace_tags: - response = self.clockapi.add_tag(task_type) - if 'id' not in response: - self.log.error('Task {} can\'t be created'.format( - task_type - )) - continue + for task_type_name in task_type_names: + if task_type_name in clockify_workspace_tags: + self.log.debug( + "Task \"{}\" already exist".format(task_type_name) + ) + continue + + response = self.clockapi.add_tag(task_type_name) + if "id" not in response: + self.log.warning( + "Task \"{}\" can't be created. Response: {}".format( + task_type_name, response + ) + ) + except Exception: job['status'] = 'failed' session.commit() From 6d8148af6b308464a477f91abbdd0715c4230592 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:23:51 +0200 Subject: [PATCH 069/947] not successfull project creation log full response from clockify --- .../ftrack_actions/action_clockify_sync.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 2047bb69bb..4c1261e817 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -81,13 +81,18 @@ class SyncClocify(BaseAction): clockify_projects = self.clockapi.get_projects() if project_name not in clockify_projects: response = self.clockapi.add_project(project_name) - if 'id' not in response: - self.log.error('Project {} can\'t be created'.format( - project_name - )) + if "id" not in response: + self.log.warning( + "Project \"{}\" can't be created. Response: {}".format( + project_name, response + ) + ) return { - 'success': False, - 'message': 'Can\'t create project, unexpected error' + "success": False, + "message": ( + "Can't create clockify project \"{}\"." + " Unexpected error." + ).format(project_name) } clockify_workspace_tags = self.clockapi.get_tags() From a8db8cd241e60d808e587ad61a1349e2ec43cb63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:25:22 +0200 Subject: [PATCH 070/947] removed unused imports --- .../clockify/ftrack_actions/action_clockify_sync.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 4c1261e817..d3537d475d 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -1,10 +1,5 @@ -import os -import sys -import argparse -import logging import json -import ftrack_api -from pype.modules.ftrack.lib import BaseAction, MissingPermision +from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.modules.clockify import ClockifyAPI @@ -20,9 +15,8 @@ class SyncClocify(BaseAction): #: roles that are allowed to register this action role_list = ["Pypeclub", "Administrator", "project Manager"] #: icon - icon = '{}/app_icons/clockify-white.png'.format( - os.environ.get('PYPE_STATICS_SERVER', '') - ) + icon = statics_icon("ftrack", "app_icons", "clockify-white.png") + #: CLockifyApi clockapi = ClockifyAPI() From 2fdf6a4ec1e2253370b1bc4a052e233c8b7929b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 11:28:39 +0200 Subject: [PATCH 071/947] undo changes in ftrack.lib.__init__ --- pype/modules/ftrack/lib/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/modules/ftrack/lib/__init__.py b/pype/modules/ftrack/lib/__init__.py index 9de477059a..df546ab725 100644 --- a/pype/modules/ftrack/lib/__init__.py +++ b/pype/modules/ftrack/lib/__init__.py @@ -1,6 +1,6 @@ from . import avalon_sync from . import credentials -from .ftrack_base_handler import BaseHandler, MissingPermision +from .ftrack_base_handler import BaseHandler from .ftrack_event_handler import BaseEvent from .ftrack_action_handler import BaseAction, statics_icon from .ftrack_app_handler import AppAction @@ -9,7 +9,6 @@ __all__ = [ "avalon_sync", "credentials", "BaseHandler", - "MissingPermision", "BaseEvent", "BaseAction", "statics_icon", From 0fccef46b82247d77ee46019ace506fcb8d3779c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 12:27:25 +0200 Subject: [PATCH 072/947] tags.json moved from presets to nukestudio host --- pype/hosts/nukestudio/tags.json | 262 ++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 pype/hosts/nukestudio/tags.json diff --git a/pype/hosts/nukestudio/tags.json b/pype/hosts/nukestudio/tags.json new file mode 100644 index 0000000000..56fcfcbce9 --- /dev/null +++ b/pype/hosts/nukestudio/tags.json @@ -0,0 +1,262 @@ +{ + "Hierarchy": { + "editable": "1", + "note": "{folder}/{sequence}/{shot}", + "icon": { + "path": "hierarchy.png" + }, + "metadata": { + "folder": "FOLDER_NAME", + "shot": "{clip}", + "track": "{track}", + "sequence": "{sequence}", + "episode": "EPISODE_NAME", + "root": "{projectroot}" + } + }, + "Source Resolution": { + "editable": "1", + "note": "Use source resolution", + "icon": { + "path": "resolution.png" + }, + "metadata": { + "family": "resolution" + } + }, + "Retiming": { + "editable": "1", + "note": "Clip has retime or TimeWarp effects (or multiple effects stacked on the clip)", + "icon": { + "path": "retiming.png" + }, + "metadata": { + "family": "retiming", + "marginIn": 1, + "marginOut": 1 + } + }, + "Frame start": { + "editable": "1", + "note": "Starting frame for comps. \n\n> Use `value` and add either number or write `source` (if you want to preserve source frame numbering)", + "icon": { + "path": "icons:TagBackground.png" + }, + "metadata": { + "family": "frameStart", + "value": "1001" + } + }, + "[Lenses]": { + "Set lense here": { + "editable": "1", + "note": "Adjust parameters of your lense and then drop to clip. Remember! You can always overwrite on clip", + "icon": { + "path": "lense.png" + }, + "metadata": { + "focalLengthMm": 57 + + } + } + }, + "[Subsets]": { + "Audio": { + "editable": "1", + "note": "Export with Audio", + "icon": { + "path": "volume.png" + }, + "metadata": { + "family": "audio", + "subset": "main" + } + }, + "plateFg": { + "editable": "1", + "note": "Add to publish to \"forground\" subset. Change metadata subset name if different order number", + "icon": { + "path": "z_layer_fg.png" + }, + "metadata": { + "family": "plate", + "subset": "Fg01" + } + }, + "plateBg": { + "editable": "1", + "note": "Add to publish to \"background\" subset. Change metadata subset name if different order number", + "icon": { + "path": "z_layer_bg.png" + }, + "metadata": { + "family": "plate", + "subset": "Bg01" + } + }, + "plateRef": { + "editable": "1", + "note": "Add to publish to \"reference\" subset.", + "icon": { + "path": "icons:Reference.png" + }, + "metadata": { + "family": "plate", + "subset": "Ref" + } + }, + "plateMain": { + "editable": "1", + "note": "Add to publish to \"main\" subset.", + "icon": { + "path": "z_layer_main.png" + }, + "metadata": { + "family": "plate", + "subset": "main" + } + }, + "plateProxy": { + "editable": "1", + "note": "Add to publish to \"proxy\" subset.", + "icon": { + "path": "z_layer_main.png" + }, + "metadata": { + "family": "plate", + "subset": "proxy" + } + }, + "review": { + "editable": "1", + "note": "Upload to Ftrack as review component.", + "icon": { + "path": "review.png" + }, + "metadata": { + "family": "review", + "track": "review" + } + } + }, + "[Handles]": { + "start: add 20 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "20", + "args": "{'op':'add','where':'start'}" + } + }, + "start: add 10 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "10", + "args": "{'op':'add','where':'start'}" + } + }, + "start: add 5 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "5", + "args": "{'op':'add','where':'start'}" + } + }, + "start: add 0 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "0", + "args": "{'op':'add','where':'start'}" + } + }, + "end: add 20 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "20", + "args": "{'op':'add','where':'end'}" + } + }, + "end: add 10 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "10", + "args": "{'op':'add','where':'end'}" + } + }, + "end: add 5 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "5", + "args": "{'op':'add','where':'end'}" + } + }, + "end: add 0 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "0", + "args": "{'op':'add','where':'end'}" + } + } + }, + "NukeScript": { + "editable": "1", + "note": "Collecting track items to Nuke scripts.", + "icon": { + "path": "icons:TagNuke.png" + }, + "metadata": { + "family": "nukescript", + "subset": "main" + } + }, + "Comment": { + "editable": "1", + "note": "Comment on a shot.", + "icon": { + "path": "icons:TagComment.png" + }, + "metadata": { + "family": "comment", + "subset": "main" + } + } +} From aa6a7bc79429d42cb8044d34553feb773142f8ca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 12:31:01 +0200 Subject: [PATCH 073/947] nukestudio tags.py load tags data from json next to it instead of using presets --- pype/hosts/nukestudio/tags.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pype/hosts/nukestudio/tags.py b/pype/hosts/nukestudio/tags.py index c97f13d17c..c2b1d0d728 100644 --- a/pype/hosts/nukestudio/tags.py +++ b/pype/hosts/nukestudio/tags.py @@ -1,16 +1,22 @@ import re import os +import json import hiero -from pype.api import ( - config, - Logger -) +from pype.api import Logger from avalon import io log = Logger().get_logger(__name__, "nukestudio") +def tag_data(): + current_dir = os.path.dirname(__file__) + json_path = os.path.join(current_dir, "tags.json") + with open(json_path, "r") as json_stream: + data = json.load(json_stream) + return data + + def create_tag(key, value): """ Creating Tag object. @@ -58,13 +64,9 @@ def add_tags_from_presets(): return log.debug("Setting default tags on project: {}".format(project.name())) - - # get all presets - presets = config.get_presets() - # get nukestudio tag.json from presets - nks_pres = presets["nukestudio"] - nks_pres_tags = nks_pres.get("tags", None) + # get nukestudio tags.json + nks_pres_tags = tag_data() # Get project task types. tasks = io.find_one({"type": "project"})["config"]["tasks"] From ce403015014c534a50ba245d7e370a57e41a984b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 14 Jul 2020 12:31:24 +0200 Subject: [PATCH 074/947] validator for Maya: check if instances asset matches context asset --- .../publish/validate_instance_in_context.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 pype/plugins/maya/publish/validate_instance_in_context.py diff --git a/pype/plugins/maya/publish/validate_instance_in_context.py b/pype/plugins/maya/publish/validate_instance_in_context.py new file mode 100644 index 0000000000..ea83c2d3af --- /dev/null +++ b/pype/plugins/maya/publish/validate_instance_in_context.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +"""Validate if instance asset is the same as context asset.""" +from __future__ import absolute_import +import pyblish.api +from pype.action import get_errored_instances_from_context +import pype.api + + +class SelectInvalidInstances(pyblish.api.Action): + """Select invalid instances in Outliner.""" + + label = "Show Instances" + icon = "briefcase" + on = "failed" + + def process(self, context, plugin): + """Process invalid validators and select invalid instances.""" + try: + from maya import cmds + except ImportError: + raise ImportError("Current host is not Maya") + + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes..") + invalid = list() + for instance in instances: + invalid_instances = plugin.get_invalid(context) + if invalid_instances: + if isinstance(invalid_instances, (list, tuple)): + invalid.extend(invalid_instances) + else: + self.log.warning("Plug-in returned to be invalid, " + "but has no selectable nodes.") + + # Ensure unique (process each node only once) + invalid = list(set(invalid)) + + if invalid: + self.log.info("Selecting invalid nodes: %s" % ", ".join(invalid)) + cmds.select(invalid, replace=True, noExpand=True) + else: + self.log.info("No invalid nodes found.") + cmds.select(deselect=True) + + +class ValidateInstanceInContext(pyblish.api.ContextPlugin): + """Validator to check if instance asset match context asset. + + When working in per-shot style you always publish data in context of + current asset (shot). This validator checks if this is so. It is optional + so it can be disabled when needed. + + Action on this validator will select invalid instances in Outliner. + """ + + order = pype.api.ValidateContentsOrder + label = "Instance in same Context" + optional = True + actions = [SelectInvalidInstances] + + @classmethod + def get_invalid(cls, context): + """Get invalid instances.""" + invalid = [] + context_asset = context.data["assetEntity"]["name"] + cls.log.info("we are in {}".format(context_asset)) + for instance in context: + asset = instance.data.get("asset") + if asset != context_asset: + cls.log.warning("{} has asset {}".format(instance.name, asset)) + invalid.append(instance.name) + + return invalid + + def process(self, context): + """Check instances.""" + invalid = self.get_invalid(context) + if invalid: + raise AssertionError("Some instances doesn't share same context") From 05cc4385dfbff2eb1f022f75f2cc8091161dc80c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jul 2020 15:04:42 +0200 Subject: [PATCH 075/947] check if last workfile exist before env variable is set --- pype/modules/ftrack/lib/ftrack_app_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 34ab8c5ee4..efc2df0ea5 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -216,7 +216,7 @@ class AppAction(BaseAction): "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir }) - if last_workfile_path: + if last_workfile_path and os.path.exists(last_workfile_path): prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path prep_env.update(anatomy.roots_obj.root_environments()) From 21df2093d56ff8c199457ae8c77577a8c0736152 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Wed, 15 Jul 2020 12:23:04 +0100 Subject: [PATCH 076/947] Fixed problem with local objects --- pype/hosts/blender/plugin.py | 5 ++-- pype/plugins/blender/load/load_camera.py | 35 +++++++++++++++--------- pype/plugins/blender/load/load_layout.py | 17 ++++++------ pype/plugins/blender/load/load_model.py | 10 +++---- pype/plugins/blender/load/load_rig.py | 32 ++++++++++++---------- 5 files changed, 56 insertions(+), 43 deletions(-) diff --git a/pype/hosts/blender/plugin.py b/pype/hosts/blender/plugin.py index ab53d49041..07080a86c4 100644 --- a/pype/hosts/blender/plugin.py +++ b/pype/hosts/blender/plugin.py @@ -45,8 +45,9 @@ def get_unique_number( def prepare_data(data, container_name): name = data.name - data = data.make_local() - data.name = f"{name}:{container_name}" + local_data = data.make_local() + local_data.name = f"{name}:{container_name}" + return local_data def create_blender_context(active: Optional[bpy.types.Object] = None, diff --git a/pype/plugins/blender/load/load_camera.py b/pype/plugins/blender/load/load_camera.py index 7fd8f94b4e..2cd9cd7b34 100644 --- a/pype/plugins/blender/load/load_camera.py +++ b/pype/plugins/blender/load/load_camera.py @@ -51,26 +51,26 @@ class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader): objects_list = [] for obj in camera_container.objects: - obj = obj.make_local() - obj.data.make_local() + local_obj = obj.make_local() + local_obj.data.make_local() - if not obj.get(blender.pipeline.AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() + if not local_obj.get(blender.pipeline.AVALON_PROPERTY): + local_obj[blender.pipeline.AVALON_PROPERTY] = dict() - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info = local_obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) if actions[0] is not None: - if obj.animation_data is None: - obj.animation_data_create() - obj.animation_data.action = actions[0] + if local_obj.animation_data is None: + local_obj.animation_data_create() + local_obj.animation_data.action = actions[0] if actions[1] is not None: - if obj.data.animation_data is None: - obj.data.animation_data_create() - obj.data.animation_data.action = actions[1] + if local_obj.data.animation_data is None: + local_obj.data.animation_data_create() + local_obj.data.animation_data.action = actions[1] - objects_list.append(obj) + objects_list.append(local_obj) camera_container.pop(blender.pipeline.AVALON_PROPERTY) @@ -190,7 +190,16 @@ class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader): camera = objects[0] - actions = (camera.animation_data.action, camera.data.animation_data.action) + camera_action = None + camera_data_action = None + + if camera.animation_data and camera.animation_data.action: + camera_action = camera.animation_data.action + + if camera.data.animation_data and camera.data.animation_data.action: + camera_data_action = camera.data.animation_data.action + + actions = (camera_action, camera_data_action) self._remove(objects, lib_container) diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index cfab5a207b..d3bf881bc1 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -79,21 +79,21 @@ class BlendLayoutLoader(plugin.AssetLoader): # The armature is unparented for all the non-local meshes, # when it is made local. for obj in objects + armatures: - obj.make_local() + local_obj = obj.make_local() if obj.data: obj.data.make_local() - if not obj.get(blender.pipeline.AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() + if not local_obj.get(blender.pipeline.AVALON_PROPERTY): + local_obj[blender.pipeline.AVALON_PROPERTY] = dict() - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info = local_obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) - action = actions.get(obj.name, None) + action = actions.get(local_obj.name, None) + + if local_obj.type == 'ARMATURE' and action is not None: + local_obj.animation_data.action = action - if obj.type == 'ARMATURE' and action is not None: - obj.animation_data.action = action - layout_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') @@ -222,6 +222,7 @@ class BlendLayoutLoader(plugin.AssetLoader): for obj in objects: if obj.type == 'ARMATURE': + if obj.animation_data and obj.animation_data.action: actions[obj.name] = obj.animation_data.action self._remove(objects, obj_container) diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index ad9137a15d..5e5bee1e6e 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -53,16 +53,16 @@ class BlendModelLoader(plugin.AssetLoader): model_container.name = container_name for obj in model_container.objects: - plugin.prepare_data(obj, container_name) - plugin.prepare_data(obj.data, container_name) + local_obj = plugin.prepare_data(obj, container_name) + plugin.prepare_data(local_obj.data, container_name) - for material_slot in obj.material_slots: + for material_slot in local_obj.material_slots: plugin.prepare_data(material_slot.material, container_name) if not obj.get(blender.pipeline.AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() + local_obj[blender.pipeline.AVALON_PROPERTY] = dict() - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] + avalon_info = local_obj[blender.pipeline.AVALON_PROPERTY] avalon_info.update({"container_name": container_name}) model_container.pop(blender.pipeline.AVALON_PROPERTY) diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index e09a9cb92f..a9fb0c18f1 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -63,25 +63,25 @@ class BlendRigLoader(plugin.AssetLoader): ] for child in rig_container.children: - plugin.prepare_data(child, container_name) - meshes.extend(child.objects) + local_child = plugin.prepare_data(child, container_name) + meshes.extend(local_child.objects) # Link meshes first, then armatures. # The armature is unparented for all the non-local meshes, # when it is made local. for obj in meshes + armatures: - plugin.prepare_data(obj, container_name) - plugin.prepare_data(obj.data, container_name) - - if not obj.get(blender.pipeline.AVALON_PROPERTY): - obj[blender.pipeline.AVALON_PROPERTY] = dict() - - avalon_info = obj[blender.pipeline.AVALON_PROPERTY] - avalon_info.update({"container_name": container_name}) - - if obj.type == 'ARMATURE' and action is not None: - obj.animation_data.action = action - + local_obj = plugin.prepare_data(obj, container_name) + plugin.prepare_data(local_obj.data, container_name) + + if not local_obj.get(blender.pipeline.AVALON_PROPERTY): + local_obj[blender.pipeline.AVALON_PROPERTY] = dict() + + avalon_info = local_obj[blender.pipeline.AVALON_PROPERTY] + avalon_info.update({"container_name": container_name}) + + if local_obj.type == 'ARMATURE' and action is not None: + local_obj.animation_data.action = action + rig_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') @@ -214,7 +214,9 @@ class BlendRigLoader(plugin.AssetLoader): armatures = [obj for obj in objects if obj.type == 'ARMATURE'] assert(len(armatures) == 1) - action = armatures[0].animation_data.action + action = None + if armatures[0].animation_data and armatures[0].animation_data.action: + action = armatures[0].animation_data.action parent = plugin.get_parent_collection(obj_container) From ae043d5a65c940f2c67c5fc1f6f6ec037af54b04 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jul 2020 15:01:55 +0200 Subject: [PATCH 077/947] fix icon path --- pype/modules/clockify/ftrack_actions/action_clockify_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index d3537d475d..c464a38e56 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -15,7 +15,7 @@ class SyncClocify(BaseAction): #: roles that are allowed to register this action role_list = ["Pypeclub", "Administrator", "project Manager"] #: icon - icon = statics_icon("ftrack", "app_icons", "clockify-white.png") + icon = statics_icon("app_icons", "clockify-white.png") #: CLockifyApi clockapi = ClockifyAPI() From 6ea84a04e24094c6f53a5e53d240007d60469a6d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jul 2020 15:02:08 +0200 Subject: [PATCH 078/947] fix job status on crash --- .../clockify/ftrack_actions/action_clockify_sync.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index c464a38e56..654ed91b0e 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -106,11 +106,14 @@ class SyncClocify(BaseAction): ) except Exception: - job['status'] = 'failed' - session.commit() - return False + pass - job['status'] = 'done' + finally: + if job["status"] != "done": + job["status"] = "failed" + session.commit() + + job["status"] = "done" session.commit() return True From 0cdf9ac479d2ce0bf5d5258010502ac29251cec5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jul 2020 15:02:20 +0200 Subject: [PATCH 079/947] set estimate for new project --- pype/modules/clockify/clockify_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index f012efc002..64ed512f57 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -363,7 +363,7 @@ class ClockifyAPI(metaclass=Singleton): "clientId": "", "isPublic": "false", "estimate": { - # "estimate": "3600", + "estimate": "0", "type": "AUTO" }, "color": "#f44336", From f2fdd9ca0a64a2651503977b46977900c7d98c71 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jul 2020 15:22:08 +0200 Subject: [PATCH 080/947] job status fix --- pype/modules/clockify/ftrack_actions/action_clockify_sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py index 654ed91b0e..0ba4c3a265 100644 --- a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack_actions/action_clockify_sync.py @@ -105,6 +105,8 @@ class SyncClocify(BaseAction): ) ) + job["status"] = "done" + except Exception: pass @@ -113,8 +115,6 @@ class SyncClocify(BaseAction): job["status"] = "failed" session.commit() - job["status"] = "done" - session.commit() return True From 4d1f5b12dce60b5f26f4b559f843c09763b433e4 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 16 Jul 2020 11:44:02 +0100 Subject: [PATCH 081/947] Fix preview always True. --- pype/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 7a73e921e2..4f32e37c17 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -488,7 +488,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if bake_render_path: preview = False - if "celaction" in self.hosts: + if "celaction" in pyblish.api.registered_hosts(): preview = True staging = os.path.dirname(list(collection)[0]) From 8aed10d1b522f63141ff053d6af80d9b4e3a33c7 Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 16 Jul 2020 14:17:33 +0100 Subject: [PATCH 082/947] Fixed indentation --- pype/plugins/blender/load/load_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/blender/load/load_layout.py b/pype/plugins/blender/load/load_layout.py index d3bf881bc1..c23cdbd354 100644 --- a/pype/plugins/blender/load/load_layout.py +++ b/pype/plugins/blender/load/load_layout.py @@ -223,7 +223,7 @@ class BlendLayoutLoader(plugin.AssetLoader): for obj in objects: if obj.type == 'ARMATURE': if obj.animation_data and obj.animation_data.action: - actions[obj.name] = obj.animation_data.action + actions[obj.name] = obj.animation_data.action self._remove(objects, obj_container) From 91786b5206edd810b77b3e7b5e0193b6d9106cb3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jul 2020 14:45:51 +0100 Subject: [PATCH 083/947] fix(celaction): support for space in folder or file name --- pype/hooks/celaction/prelaunch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index df9da6cbbf..e1e86cc919 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -106,7 +106,7 @@ class CelactionPrelaunchHook(PypeHook): f"--project {project}", f"--asset {asset}", f"--task {task}", - "--currentFile \"*SCENE*\"", + "--currentFile \\\"\"*SCENE*\"\\\"", "--chunk *CHUNK*", "--frameStart *START*", "--frameEnd *END*", From 027ac520045cc26d449dd29f65b014747477c1e9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 Jul 2020 14:46:25 +0100 Subject: [PATCH 084/947] fix(celaction): correct host path --- pype/hosts/celaction/cli.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pype/hosts/celaction/cli.py b/pype/hosts/celaction/cli.py index 8cf2bcc791..42f7a1a385 100644 --- a/pype/hosts/celaction/cli.py +++ b/pype/hosts/celaction/cli.py @@ -46,9 +46,6 @@ def cli(): parser.add_argument("--resolutionHeight", help=("Height of resolution")) - # parser.add_argument("--programDir", - # help=("Directory with celaction program installation")) - celaction.kwargs = parser.parse_args(sys.argv[1:]).__dict__ @@ -78,7 +75,7 @@ def _prepare_publish_environments(): env["AVALON_WORKDIR"] = os.getenv("AVALON_WORKDIR") env["AVALON_HIERARCHY"] = hierarchy env["AVALON_PROJECTCODE"] = project_doc["data"].get("code", "") - env["AVALON_APP"] = publish_host + env["AVALON_APP"] = f"hosts.{publish_host}" env["AVALON_APP_NAME"] = "celaction_local" env["PYBLISH_HOSTS"] = publish_host From 5f53213dc5fbd70cd263f54e6db4f41728ca603b Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 16 Jul 2020 17:22:54 +0200 Subject: [PATCH 085/947] added mac stuff to gitignore --- .gitignore | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.gitignore b/.gitignore index 4b2eb5453a..101c1e6224 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,31 @@ __pycache__/ *.py[cod] *$py.class +# Mac Stuff +########### +# General +.DS_Store +.AppleDouble +.LSOverride +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + # Documentation ############### /docs/build From 59608daac1017a602ce2791e95d091592b1e9dda Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 Jul 2020 11:50:07 +0100 Subject: [PATCH 086/947] fix(global): remove duplicate code for copy files --- pype/plugins/global/publish/integrate_new.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 9f20999f55..d151cfc608 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -514,12 +514,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): instance: the instance to integrate """ transfers = instance.data.get("transfers", list()) - - for src, dest in transfers: - if os.path.normpath(src) != os.path.normpath(dest): - self.copy_file(src, dest) - - transfers = instance.data.get("transfers", list()) for src, dest in transfers: self.copy_file(src, dest) From f62dbe26721d557ac0e3397bcd48a2057a96e499 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 17 Jul 2020 16:10:43 +0200 Subject: [PATCH 087/947] change string attribute to ingeter in project sync --- pype/modules/clockify/clockify_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 64ed512f57..86365a9352 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -363,7 +363,7 @@ class ClockifyAPI(metaclass=Singleton): "clientId": "", "isPublic": "false", "estimate": { - "estimate": "0", + "estimate": 0, "type": "AUTO" }, "color": "#f44336", From ab9e1637fe4ee07eea5ba590c2000866e61a61a9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jul 2020 16:14:30 +0200 Subject: [PATCH 088/947] use constant from ftrack.lib for custom attribute name --- pype/plugins/premiere/publish/validate_auto_sync_off.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index 1f3f0b58a5..ca75a4d14e 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -2,7 +2,11 @@ import sys import pyblish.api import pype.api import avalon.api -import six + +try: + from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC +except Exception: + CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" class ValidateAutoSyncOff(pyblish.api.ContextPlugin): From f7304ba26d6127410a7dab1b9577135ac8593ff9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jul 2020 16:14:48 +0200 Subject: [PATCH 089/947] do not query project in process part (not used) --- pype/plugins/premiere/publish/validate_auto_sync_off.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index ca75a4d14e..53042fef6a 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -23,12 +23,7 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin): actions = [pype.api.RepairAction] def process(self, context): - session = context.data["ftrackSession"] - project_name = avalon.api.Session["AVALON_PROJECT"] - query = 'Project where full_name is "{}"'.format(project_name) - project = session.query(query).one() invalid = self.get_invalid(context) - assert not invalid, ( "Ftrack Project has 'Auto sync' set to On." " That may cause issues during integration." From 00acee01ce3c5b62d545ed4c58168fe94045aa13 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jul 2020 16:15:10 +0200 Subject: [PATCH 090/947] get_invalid returns project only if auto sync is turned on --- pype/plugins/premiere/publish/validate_auto_sync_off.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index 53042fef6a..3b7937bbec 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -35,8 +35,8 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin): project_name = avalon.api.Session["AVALON_PROJECT"] query = 'Project where full_name is "{}"'.format(project_name) project = session.query(query).one() - - return project + if project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: + return project @classmethod def repair(cls, context): From 7f2d6a7d8518c5205f33fb5d10c98dce3093ef3d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jul 2020 16:15:52 +0200 Subject: [PATCH 091/947] one more place where constant for custom attribute key is used --- pype/plugins/premiere/publish/validate_auto_sync_off.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index 3b7937bbec..dc4420a9f7 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -42,7 +42,7 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin): def repair(cls, context): session = context.data["ftrackSession"] invalid = cls.get_invalid(context) - invalid['custom_attributes']['avalon_auto_sync'] = False + invalid["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False try: session.commit() except Exception: From fdaf486ef5ddbe01862373312878a76b9b92d377 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jul 2020 16:21:11 +0200 Subject: [PATCH 092/947] added additional check to repair for sure --- pype/plugins/premiere/publish/validate_auto_sync_off.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index dc4420a9f7..7a5d78795a 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -42,6 +42,10 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin): def repair(cls, context): session = context.data["ftrackSession"] invalid = cls.get_invalid(context) + if not invalid: + cls.log.info("Project 'Auto sync' already fixed.") + return + invalid["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False try: session.commit() From e879fe78958be659fadf8315745e3a7da3459a31 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jul 2020 16:21:25 +0200 Subject: [PATCH 093/947] changed ReparAction to RepairContextAction --- pype/plugins/premiere/publish/validate_auto_sync_off.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py index 7a5d78795a..cd6fef29c8 100644 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ b/pype/plugins/premiere/publish/validate_auto_sync_off.py @@ -20,7 +20,7 @@ class ValidateAutoSyncOff(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder families = ['clip'] label = 'Ftrack project\'s auto sync off' - actions = [pype.api.RepairAction] + actions = [pype.api.RepairContextAction] def process(self, context): invalid = self.get_invalid(context) From 62e296de6d1a986d19ae41a88bf2021ac93a69f4 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 17 Jul 2020 17:52:16 +0100 Subject: [PATCH 094/947] Using PYPE_FTRACK_SOCKET_TIMEOUT variable and default 45. --- pype/modules/ftrack/ftrack_server/socket_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/socket_thread.py b/pype/modules/ftrack/ftrack_server/socket_thread.py index 3602f2f138..e66e8bc775 100644 --- a/pype/modules/ftrack/ftrack_server/socket_thread.py +++ b/pype/modules/ftrack/ftrack_server/socket_thread.py @@ -11,7 +11,7 @@ from pype.api import Logger class SocketThread(threading.Thread): """Thread that checks suprocess of storer of processor of events""" - MAX_TIMEOUT = 120 + MAX_TIMEOUT = int(os.environ.get("PYPE_FTRACK_SOCKET_TIMEOUT", 45)) def __init__(self, name, port, filepath, additional_args=[]): super(SocketThread, self).__init__() From 40b470b1cf3613dee9580d988f5df533f0c864e7 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 20 Jul 2020 11:08:16 +0200 Subject: [PATCH 095/947] update ffmpeg variable and small maya fix --- .../maya/publish/validate_transform_naming_suffix.py | 4 +--- pype/plugins/photoshop/publish/extract_review.py | 10 ++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pype/plugins/maya/publish/validate_transform_naming_suffix.py b/pype/plugins/maya/publish/validate_transform_naming_suffix.py index 17066f6b12..120123af4b 100644 --- a/pype/plugins/maya/publish/validate_transform_naming_suffix.py +++ b/pype/plugins/maya/publish/validate_transform_naming_suffix.py @@ -103,9 +103,7 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): instance (:class:`pyblish.api.Instance`): published instance. """ - invalid = self.get_invalid(instance, - self.SUFFIX_NAMING_TABLE, - self.ALLOW_IF_NOT_IN_SUFFIX_TABLE) + invalid = self.get_invalid(instance) if invalid: raise ValueError("Incorrectly named geometry " "transforms: {0}".format(invalid)) diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 49e932eb67..d784dc0998 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -42,6 +42,8 @@ class ExtractReview(pype.api.Extractor): staging_dir, photoshop.com_objects.JPEGSaveOptions(), True ) + ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + instance.data["representations"].append({ "name": "jpg", "ext": "jpg", @@ -53,13 +55,13 @@ class ExtractReview(pype.api.Extractor): # Generate thumbnail. thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") args = [ - "ffmpeg", "-y", + ffmpeg_path, "-y", "-i", os.path.join(staging_dir, output_image), "-vf", "scale=300:-1", "-vframes", "1", thumbnail_path ] - output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"]) + output = pype.lib._subprocess(args) self.log.debug(output) @@ -74,12 +76,12 @@ class ExtractReview(pype.api.Extractor): # Generate mov. mov_path = os.path.join(staging_dir, "review.mov") args = [ - "ffmpeg", "-y", + ffmpeg_path, "-y", "-i", os.path.join(staging_dir, output_image), "-vframes", "1", mov_path ] - output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"]) + output = pype.lib._subprocess(args) self.log.debug(output) From d7883fe561238073e59fde455b2d7845ec7ad7aa Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Jul 2020 11:21:47 +0100 Subject: [PATCH 096/947] Use published workfile for rendering. --- .../nuke/publish/submit_nuke_deadline.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 2b8efb4640..26d3f9b571 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -49,6 +49,24 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): render_path = instance.data['path'] script_path = context.data["currentFile"] + for item in context: + if "workfile" in item.data["families"]: + msg = "Workfile (scene) must be published along" + assert item.data["publish"] is True, msg + + template_data = item.data.get("anatomyData") + rep = item.data.get("representations")[0].get("name") + template_data["representation"] = rep + template_data["ext"] = rep + template_data["comment"] = None + anatomy_filled = context.data["anatomy"].format(template_data) + template_filled = anatomy_filled["publish"]["path"] + script_path = os.path.normpath(template_filled) + + self.log.info( + "Using published scene for render {}".format(script_path) + ) + # exception for slate workflow if "slate" in instance.data["families"]: self._frame_start -= 1 From fa91da78ee6956d04e2f3d1ca412d8ef933f622b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 20 Jul 2020 13:00:29 +0200 Subject: [PATCH 097/947] optional plugins with active attribute set to False are skipped --- pype/tools/pyblish_pype/control.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index 5138b5cc4c..77badf71b6 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -183,7 +183,18 @@ class Controller(QtCore.QObject): plugins = pyblish.api.discover() targets = pyblish.logic.registered_targets() or ["default"] - self.plugins = pyblish.logic.plugins_by_targets(plugins, targets) + plugins_by_targets = pyblish.logic.plugins_by_targets(plugins, targets) + + _plugins = [] + for plugin in plugins_by_targets: + # Skip plugin if is not optional and not active + if ( + not getattr(plugin, "optional", False) + and not getattr(plugin, "active", True) + ): + continue + _plugins.append(plugin) + self.plugins = _plugins def on_published(self): if self.is_running: From 059082a671cf414c028d64cc190b9a03272ae7e3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 11:55:27 +0200 Subject: [PATCH 098/947] fix(ppro): audio only was not working --- pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx b/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx index 684cef5e5a..3cd4502653 100644 --- a/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx +++ b/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx @@ -534,7 +534,9 @@ $.pype = { if (instances === null) { return null; } - if (audioOnly === true) { + + // make only audio representations + if (audioOnly === 'true') { $.pype.log('? looping if audio True'); for (var i = 0; i < instances.length; i++) { var subsetToRepresentations = instances[i].subsetToRepresentations; From 19a33b7272843c06a592a3fe044dc82fb806ff9e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 12:08:23 +0200 Subject: [PATCH 099/947] feat(ppro): synchronization of ftrack project back on --- .../publish/integrate_auto_sync_back_on.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pype/plugins/premiere/publish/integrate_auto_sync_back_on.py diff --git a/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py b/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py new file mode 100644 index 0000000000..ca7151d3d3 --- /dev/null +++ b/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py @@ -0,0 +1,35 @@ +import sys +import pyblish.api +import avalon.api + +try: + from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC +except Exception: + CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" + + +class IntegrateAutoSyncBackOn(pyblish.api.ContextPlugin): + """Ensure that autosync value in ftrack project is set to True. + + In case was set to False and event server with the sync to avalon event + is not running this will set synchronization back on. + """ + + order = pyblish.api.IntegratorOrder + 1 + families = ['clip'] + label = 'Ftrack project\'s auto sync on' + + def process(self, context): + session = context.data["ftrackSession"] + project_name = avalon.api.Session["AVALON_PROJECT"] + query = 'Project where full_name is "{}"'.format(project_name) + project = session.query(query).one() + if not project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + + try: + session.commit() + except Exception: + tp, value, tb = sys.exc_info() + session.rollback() + raise From 60a9b6bface827b1190d8108f55398b59eab16d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 14:27:55 +0200 Subject: [PATCH 100/947] feat(ftrack): auto-sync switcher moved to frack hierarchy integrator --- .../publish/integrate_hierarchy_ftrack.py | 49 ++++++++++++++++- .../publish/integrate_auto_sync_back_on.py | 35 ------------ .../publish/validate_auto_sync_off.py | 55 ------------------- 3 files changed, 47 insertions(+), 92 deletions(-) delete mode 100644 pype/plugins/premiere/publish/integrate_auto_sync_back_on.py delete mode 100644 pype/plugins/premiere/publish/validate_auto_sync_off.py diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index a12fdfd36c..7a43daf781 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -1,9 +1,13 @@ import sys - import six import pyblish.api from avalon import io +try: + from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC +except Exception: + CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" + class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): """ @@ -47,7 +51,16 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): input_data = context.data["hierarchyContext"] - self.import_to_ftrack(input_data) + # disable termporarily ftrack project's autosyncing + self.auto_sync_off(context) + + try: + # import ftrack hierarchy + self.import_to_ftrack(input_data) + except Exception: + raise + finally: + self.auto_sync_on() def import_to_ftrack(self, input_data, parent=None): for entity_name in input_data: @@ -217,3 +230,35 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) return entity + + def auto_sync_off(self, context): + project_name = context.data["projectEntity"]["name"] + query = 'Project where full_name is "{}"'.format(project_name) + self.project = self.session.query(query).one() + self.auto_sync_state = self.project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] + + self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + raise + + self.log.info("Ftrack autosync swithed off") + + def auto_sync_on(self): + if not self.project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] \ + and self.auto_sync_state: + self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + self.log.info("Ftrack autosync swithed on") + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + raise diff --git a/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py b/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py deleted file mode 100644 index ca7151d3d3..0000000000 --- a/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py +++ /dev/null @@ -1,35 +0,0 @@ -import sys -import pyblish.api -import avalon.api - -try: - from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC -except Exception: - CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" - - -class IntegrateAutoSyncBackOn(pyblish.api.ContextPlugin): - """Ensure that autosync value in ftrack project is set to True. - - In case was set to False and event server with the sync to avalon event - is not running this will set synchronization back on. - """ - - order = pyblish.api.IntegratorOrder + 1 - families = ['clip'] - label = 'Ftrack project\'s auto sync on' - - def process(self, context): - session = context.data["ftrackSession"] - project_name = avalon.api.Session["AVALON_PROJECT"] - query = 'Project where full_name is "{}"'.format(project_name) - project = session.query(query).one() - if not project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: - project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True - - try: - session.commit() - except Exception: - tp, value, tb = sys.exc_info() - session.rollback() - raise diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py deleted file mode 100644 index cd6fef29c8..0000000000 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ /dev/null @@ -1,55 +0,0 @@ -import sys -import pyblish.api -import pype.api -import avalon.api - -try: - from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC -except Exception: - CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" - - -class ValidateAutoSyncOff(pyblish.api.ContextPlugin): - """Ensure that autosync value in ftrack project is set to False. - - In case was set to True and event server with the sync to avalon event - is running will cause integration to avalon will be override. - - """ - - order = pyblish.api.ValidatorOrder - families = ['clip'] - label = 'Ftrack project\'s auto sync off' - actions = [pype.api.RepairContextAction] - - def process(self, context): - invalid = self.get_invalid(context) - assert not invalid, ( - "Ftrack Project has 'Auto sync' set to On." - " That may cause issues during integration." - ) - - @staticmethod - def get_invalid(context): - session = context.data["ftrackSession"] - project_name = avalon.api.Session["AVALON_PROJECT"] - query = 'Project where full_name is "{}"'.format(project_name) - project = session.query(query).one() - if project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: - return project - - @classmethod - def repair(cls, context): - session = context.data["ftrackSession"] - invalid = cls.get_invalid(context) - if not invalid: - cls.log.info("Project 'Auto sync' already fixed.") - return - - invalid["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False - try: - session.commit() - except Exception: - tp, value, tb = sys.exc_info() - session.rollback() - raise From 45e2de4d08227ebd1634f1cde8bdf2d7bb79dbde Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 14:35:48 +0200 Subject: [PATCH 101/947] fix(ftrack): refactoring code --- .../publish/integrate_hierarchy_ftrack.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 7a43daf781..05908cb2da 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -43,6 +43,12 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: return + project_name = context.data["projectEntity"]["name"] + query = 'Project where full_name is "{}"'.format(project_name) + project = self.session.query(query).one() + auto_sync_state = project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] + if not io.Session: io.install() @@ -52,7 +58,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): input_data = context.data["hierarchyContext"] # disable termporarily ftrack project's autosyncing - self.auto_sync_off(context) + if auto_sync_state: + self.auto_sync_off(project) try: # import ftrack hierarchy @@ -60,7 +67,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: raise finally: - self.auto_sync_on() + if auto_sync_state: + self.auto_sync_on(project) def import_to_ftrack(self, input_data, parent=None): for entity_name in input_data: @@ -231,30 +239,23 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): return entity - def auto_sync_off(self, context): - project_name = context.data["projectEntity"]["name"] - query = 'Project where full_name is "{}"'.format(project_name) - self.project = self.session.query(query).one() - self.auto_sync_state = self.project[ - "custom_attributes"][CUST_ATTR_AUTO_SYNC] - - self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False - - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - raise + def auto_sync_off(self, project): + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False self.log.info("Ftrack autosync swithed off") - def auto_sync_on(self): - if not self.project[ - "custom_attributes"][CUST_ATTR_AUTO_SYNC] \ - and self.auto_sync_state: - self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True - self.log.info("Ftrack autosync swithed on") + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + raise + + def auto_sync_on(self, project): + + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + + self.log.info("Ftrack autosync swithed on") try: self.session.commit() From eb28ba7a5f90f746b7835fdec97b2d957cf14089 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 14:40:56 +0200 Subject: [PATCH 102/947] feat(ftrack): fix session missing --- pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 05908cb2da..a0059c55a6 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -43,7 +43,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: return - project_name = context.data["projectEntity"]["name"] + self.session = self.context.data["ftrackSession"] + project_name = self.context.data["projectEntity"]["name"] query = 'Project where full_name is "{}"'.format(project_name) project = self.session.query(query).one() auto_sync_state = project[ @@ -53,7 +54,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): io.install() self.ft_project = None - self.session = context.data["ftrackSession"] input_data = context.data["hierarchyContext"] From e7d84d595bf65fe7031623ca092f5b95f7f61107 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 21 Jul 2020 15:31:29 +0200 Subject: [PATCH 103/947] added repair function --- .../publish/validate_instance_in_context.py | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/publish/validate_instance_in_context.py b/pype/plugins/maya/publish/validate_instance_in_context.py index ea83c2d3af..542249bb2d 100644 --- a/pype/plugins/maya/publish/validate_instance_in_context.py +++ b/pype/plugins/maya/publish/validate_instance_in_context.py @@ -28,7 +28,7 @@ class SelectInvalidInstances(pyblish.api.Action): # Get the invalid nodes for the plug-ins self.log.info("Finding invalid nodes..") invalid = list() - for instance in instances: + for _instance in instances: invalid_instances = plugin.get_invalid(context) if invalid_instances: if isinstance(invalid_instances, (list, tuple)): @@ -48,6 +48,30 @@ class SelectInvalidInstances(pyblish.api.Action): cmds.select(deselect=True) +class RepairSelectInvalidInstances(pyblish.api.Action): + """Repair the instance asset.""" + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + from maya import cmds + # Get the errored instances + failed = [] + for result in context.data["results"]: + if (result["error"] is not None and result["instance"] is not None + and result["instance"] not in failed): + failed.append(result["instance"]) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(failed, plugin) + context_asset = context.data["assetEntity"]["name"] + for instance in instances: + cmds.setAttr(instance.data.get("name") + ".asset", + context_asset, type="string") + + class ValidateInstanceInContext(pyblish.api.ContextPlugin): """Validator to check if instance asset match context asset. @@ -61,7 +85,7 @@ class ValidateInstanceInContext(pyblish.api.ContextPlugin): order = pype.api.ValidateContentsOrder label = "Instance in same Context" optional = True - actions = [SelectInvalidInstances] + actions = [SelectInvalidInstances, RepairSelectInvalidInstances] @classmethod def get_invalid(cls, context): From ad25719c79802fc966a801dc5b965d5ef91f8a2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 21 Jul 2020 17:04:16 +0200 Subject: [PATCH 104/947] minor preparetion tweaks --- pype/modules/ftrack/lib/ftrack_app_handler.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index efc2df0ea5..6a40c1a382 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -142,6 +142,9 @@ class AppAction(BaseAction): """ entity = entities[0] + + task_name = entity["name"] + project_name = entity["project"]["full_name"] database = pypelib.get_avalon_database() @@ -164,7 +167,7 @@ class AppAction(BaseAction): "name": entity["project"]["full_name"], "code": entity["project"]["name"] }, - "task": entity["name"], + "task": task_name, "asset": asset_name, "app": host_name, "hierarchy": hierarchy @@ -210,8 +213,8 @@ class AppAction(BaseAction): prep_env.update({ "AVALON_PROJECT": project_name, "AVALON_ASSET": asset_name, - "AVALON_TASK": entity["name"], - "AVALON_APP": self.identifier.split("_")[0], + "AVALON_TASK": task_name, + "AVALON_APP": host_name, "AVALON_APP_NAME": self.identifier, "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir From d758d93b507b32fbac56bc82763105419f6bee84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 21 Jul 2020 17:05:01 +0200 Subject: [PATCH 105/947] ftrack app handler is using `should_start_last_workfile` from avalon and sets variables the same way --- pype/modules/ftrack/lib/ftrack_app_handler.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 6a40c1a382..7ba7ad3f86 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -8,7 +8,9 @@ import getpass from pype import lib as pypelib from pype.api import config, Anatomy from .ftrack_action_handler import BaseAction -from avalon.api import last_workfile, HOST_WORKFILE_EXTENSIONS +from avalon.api import ( + last_workfile, HOST_WORKFILE_EXTENSIONS, should_start_last_workfile +) class AppAction(BaseAction): @@ -219,8 +221,22 @@ class AppAction(BaseAction): "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir }) - if last_workfile_path and os.path.exists(last_workfile_path): + + start_last_workfile = should_start_last_workfile( + project_name, host_name, task_name + ) + # Store boolean as "0"(False) or "1"(True) + prep_env["AVALON_OPEN_LAST_WORKFILE"] = ( + str(int(bool(start_last_workfile))) + ) + + if ( + start_last_workfile + and last_workfile_path + and os.path.exists(last_workfile_path) + ): prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path + prep_env.update(anatomy.roots_obj.root_environments()) # collect all parents from the task From c6f5eb6b4ad1045ae7a981b4e15f014e1ecddef6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 14:58:51 +0200 Subject: [PATCH 106/947] removed icon attribute from server's sync to avalon action --- pype/modules/ftrack/events/action_sync_to_avalon.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pype/modules/ftrack/events/action_sync_to_avalon.py b/pype/modules/ftrack/events/action_sync_to_avalon.py index a06b825d6a..4e119228c3 100644 --- a/pype/modules/ftrack/events/action_sync_to_avalon.py +++ b/pype/modules/ftrack/events/action_sync_to_avalon.py @@ -1,10 +1,8 @@ -import os import time import traceback from pype.modules.ftrack import BaseAction from pype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory -from pype.api import config class SyncToAvalonServer(BaseAction): @@ -38,17 +36,6 @@ class SyncToAvalonServer(BaseAction): variant = "- Sync To Avalon (Server)" #: Action description. description = "Send data from Ftrack to Avalon" - #: Action icon. - icon = "{}/ftrack/action_icons/PypeAdmin.svg".format( - os.environ.get( - "PYPE_STATICS_SERVER", - "http://localhost:{}".format( - config.get_presets().get("services", {}).get( - "rest_api", {} - ).get("default_port", 8021) - ) - ) - ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 0fc014e12f151f326f8a3e6b0cf71a3ab4b23996 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:49:48 +0200 Subject: [PATCH 107/947] add contants to clockify module --- pype/modules/clockify/constants.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 pype/modules/clockify/constants.py diff --git a/pype/modules/clockify/constants.py b/pype/modules/clockify/constants.py new file mode 100644 index 0000000000..5603bbc641 --- /dev/null +++ b/pype/modules/clockify/constants.py @@ -0,0 +1,17 @@ +import os +import appdirs + + +CLOCKIFY_FTRACK_SERVER_PATH = os.path.join( + os.path.dirname(__file__), "ftrack", "server" +) +CLOCKIFY_FTRACK_USER_PATH = os.path.join( + os.path.dirname(__file__), "ftrack", "user" +) +CREDENTIALS_PATH = os.path.normpath(os.path.join( + appdirs.user_data_dir("pype-app", "pype"), + "clockify.json" +)) + +ADMIN_PERMISSION_NAMES = ["WORKSPACE_OWN", "WORKSPACE_ADMIN"] +CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/" From 7635c680170cf1b162a7314771021ffb07bfa126 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:50:37 +0200 Subject: [PATCH 108/947] moved clockify action to ftrack/user/ structure --- .../{ftrack_actions => ftrack/user}/action_clockify_sync.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/modules/clockify/{ftrack_actions => ftrack/user}/action_clockify_sync.py (100%) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack/user/action_clockify_sync.py similarity index 100% rename from pype/modules/clockify/ftrack_actions/action_clockify_sync.py rename to pype/modules/clockify/ftrack/user/action_clockify_sync.py From a021deb5635106ec64921d6d1191292ad8ba6856 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:50:56 +0200 Subject: [PATCH 109/947] created copy of clockify sync action to server folder --- .../ftrack/server/action_clockify_sync.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 pype/modules/clockify/ftrack/server/action_clockify_sync.py diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync.py b/pype/modules/clockify/ftrack/server/action_clockify_sync.py new file mode 100644 index 0000000000..0ba4c3a265 --- /dev/null +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync.py @@ -0,0 +1,122 @@ +import json +from pype.modules.ftrack.lib import BaseAction, statics_icon +from pype.modules.clockify import ClockifyAPI + + +class SyncClocify(BaseAction): + '''Synchronise project names and task types.''' + + #: Action identifier. + identifier = 'clockify.sync' + #: Action label. + label = 'Sync To Clockify' + #: Action description. + description = 'Synchronise data to Clockify workspace' + #: roles that are allowed to register this action + role_list = ["Pypeclub", "Administrator", "project Manager"] + #: icon + icon = statics_icon("app_icons", "clockify-white.png") + + #: CLockifyApi + clockapi = ClockifyAPI() + + def discover(self, session, entities, event): + if ( + len(entities) == 1 + and entities[0].entity_type.lower() == "project" + ): + return True + return False + + def launch(self, session, entities, event): + self.clockapi.set_api() + if self.clockapi.workspace_id is None: + return { + "success": False, + "message": "Clockify Workspace or API key are not set!" + } + + if self.clockapi.validate_workspace_perm() is False: + return { + "success": False, + "message": "Missing permissions for this action!" + } + + # JOB SETTINGS + userId = event['source']['user']['id'] + user = session.query('User where id is ' + userId).one() + + job = session.create('Job', { + 'user': user, + 'status': 'running', + 'data': json.dumps({ + 'description': 'Sync Ftrack to Clockify' + }) + }) + session.commit() + + project_entity = entities[0] + if project_entity.entity_type.lower() != "project": + project_entity = self.get_project_from_entity(project_entity) + + project_name = project_entity["full_name"] + self.log.info( + "Synchronization of project \"{}\" to clockify begins.".format( + project_name + ) + ) + task_types = ( + project_entity["project_schema"]["_task_type_schema"]["types"] + ) + task_type_names = [ + task_type["name"] for task_type in task_types + ] + try: + clockify_projects = self.clockapi.get_projects() + if project_name not in clockify_projects: + response = self.clockapi.add_project(project_name) + if "id" not in response: + self.log.warning( + "Project \"{}\" can't be created. Response: {}".format( + project_name, response + ) + ) + return { + "success": False, + "message": ( + "Can't create clockify project \"{}\"." + " Unexpected error." + ).format(project_name) + } + + clockify_workspace_tags = self.clockapi.get_tags() + for task_type_name in task_type_names: + if task_type_name in clockify_workspace_tags: + self.log.debug( + "Task \"{}\" already exist".format(task_type_name) + ) + continue + + response = self.clockapi.add_tag(task_type_name) + if "id" not in response: + self.log.warning( + "Task \"{}\" can't be created. Response: {}".format( + task_type_name, response + ) + ) + + job["status"] = "done" + + except Exception: + pass + + finally: + if job["status"] != "done": + job["status"] = "failed" + session.commit() + + return True + + +def register(session, **kw): + SyncClocify(session).register() From 433a67b2dacf4e1d9c29e90cebc698af6c14ae65 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:52:56 +0200 Subject: [PATCH 110/947] ClockifyApi is not singleton anymore --- pype/modules/clockify/clockify_api.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 86365a9352..d8df2d8990 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -6,29 +6,7 @@ import datetime import appdirs -class Singleton(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super( - Singleton, cls - ).__call__(*args, **kwargs) - return cls._instances[cls] - - -class ClockifyAPI(metaclass=Singleton): - endpoint = "https://api.clockify.me/api/" - headers = {"X-Api-Key": None} - app_dir = os.path.normpath(appdirs.user_data_dir('pype-app', 'pype')) - file_name = 'clockify.json' - fpath = os.path.join(app_dir, file_name) - admin_permission_names = ['WORKSPACE_OWN', 'WORKSPACE_ADMIN'] - master_parent = None - workspace = None - workspace_id = None - - def set_master(self, master_parent): +class ClockifyAPI: self.master_parent = master_parent def verify_api(self): From b793e8bd79ce4d58031d45987c32cfcde9f65a58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:55:57 +0200 Subject: [PATCH 111/947] using endpoint constant in clockify api --- pype/modules/clockify/clockify_api.py | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index d8df2d8990..eafe95e3bd 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -3,7 +3,7 @@ import re import requests import json import datetime -import appdirs +from . import CLOCKIFY_ENDPOINT class ClockifyAPI: @@ -31,7 +31,7 @@ class ClockifyAPI: test_headers = {'X-Api-Key': api_key} action_url = 'workspaces/' response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=test_headers ) if response.status_code != 200: @@ -48,7 +48,7 @@ class ClockifyAPI: workspace_id, user_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) user_permissions = response.json() @@ -60,7 +60,7 @@ class ClockifyAPI: def get_user_id(self): action_url = 'v1/user/' response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) # this regex is neccessary: UNICODE strings are crashing @@ -120,7 +120,7 @@ class ClockifyAPI: def get_workspaces(self): action_url = 'workspaces/' response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return { @@ -132,7 +132,7 @@ class ClockifyAPI: workspace_id = self.workspace_id action_url = 'workspaces/{}/projects/'.format(workspace_id) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -147,7 +147,7 @@ class ClockifyAPI: workspace_id, project_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -158,7 +158,7 @@ class ClockifyAPI: workspace_id = self.workspace_id action_url = 'workspaces/{}/tags/'.format(workspace_id) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -173,7 +173,7 @@ class ClockifyAPI: workspace_id, project_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -255,7 +255,7 @@ class ClockifyAPI: "tagIds": tag_ids } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -272,7 +272,7 @@ class ClockifyAPI: workspace_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) try: @@ -302,7 +302,7 @@ class ClockifyAPI: "end": self.get_current_time() } response = requests.put( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -315,7 +315,7 @@ class ClockifyAPI: workspace_id = self.workspace_id action_url = 'workspaces/{}/timeEntries/'.format(workspace_id) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json()[:quantity] @@ -327,7 +327,7 @@ class ClockifyAPI: workspace_id, tid ) response = requests.delete( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json() @@ -348,7 +348,7 @@ class ClockifyAPI: "billable": "true" } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -358,7 +358,7 @@ class ClockifyAPI: action_url = 'workspaces/' body = {"name": name} response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -377,7 +377,7 @@ class ClockifyAPI: "projectId": project_id } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -391,7 +391,7 @@ class ClockifyAPI: "name": name } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -406,7 +406,7 @@ class ClockifyAPI: workspace_id, project_id ) response = requests.delete( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, ) return response.json() From a0eba1199bbb27e46b4efe26903a44f3c3b70342 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:58:10 +0200 Subject: [PATCH 112/947] added more constants in usage --- pype/modules/clockify/clockify_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index eafe95e3bd..c4eb3bbba0 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -53,7 +53,7 @@ class ClockifyAPI: ) user_permissions = response.json() for perm in user_permissions: - if perm['name'] in self.admin_permission_names: + if perm['name'] in ADMIN_PERMISSION_NAMES: return True return False @@ -102,18 +102,18 @@ class ClockifyAPI: def get_api_key(self): api_key = None try: - file = open(self.fpath, 'r') + file = open(CREDENTIALS_JSON_PATH, 'r') api_key = json.load(file).get('api_key', None) if api_key == '': api_key = None except Exception: - file = open(self.fpath, 'w') + file = open(CREDENTIALS_JSON_PATH, 'w') file.close() return api_key def save_api_key(self, api_key): data = {'api_key': api_key} - file = open(self.fpath, 'w') + file = open(CREDENTIALS_JSON_PATH, 'w') file.write(json.dumps(data)) file.close() From 50a1545d0f8cb6a811c5f2e9c895e895b1ebc31c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:58:29 +0200 Subject: [PATCH 113/947] removed attributes are in init now --- pype/modules/clockify/clockify_api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index c4eb3bbba0..8ebed302fe 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -3,11 +3,15 @@ import re import requests import json import datetime -from . import CLOCKIFY_ENDPOINT +from . import CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH class ClockifyAPI: + def __init__(self, workspace_name=None, api_key=None, master_parent=None): + self.workspace_name = workspace_name self.master_parent = master_parent + self.workspace_id = None + self.headers = {"X-Api-Key": api_key} def verify_api(self): for key, value in self.headers.items(): @@ -76,9 +80,9 @@ class ClockifyAPI: def set_workspace(self, name=None): if name is None: name = os.environ.get('CLOCKIFY_WORKSPACE', None) - self.workspace = name + self.workspace_name = name self.workspace_id = None - if self.workspace is None: + if self.workspace_name is None: return try: result = self.validate_workspace() @@ -93,7 +97,7 @@ class ClockifyAPI: def validate_workspace(self, name=None): if name is None: - name = self.workspace + name = self.workspace_name all_workspaces = self.get_workspaces() if name in all_workspaces: return all_workspaces[name] From 562fabe94df095379104bd67a4b48127b71b038d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:58:53 +0200 Subject: [PATCH 114/947] added constants to clockify init --- pype/modules/clockify/__init__.py | 7 +++++++ pype/modules/clockify/constants.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pype/modules/clockify/__init__.py b/pype/modules/clockify/__init__.py index 0ee2189fa5..0ea58b5b00 100644 --- a/pype/modules/clockify/__init__.py +++ b/pype/modules/clockify/__init__.py @@ -1,3 +1,10 @@ +from .constants import ( + CLOCKIFY_ENDPOINT, + ADMIN_PERMISSION_NAMES, + CREDENTIALS_JSON_PATH, + CLOCKIFY_FTRACK_USER_PATH, + CLOCKIFY_FTRACK_SERVER_PATH +) from .clockify_api import ClockifyAPI from .widget_settings import ClockifySettings from .widget_message import MessageWidget diff --git a/pype/modules/clockify/constants.py b/pype/modules/clockify/constants.py index 5603bbc641..38ad4b64cf 100644 --- a/pype/modules/clockify/constants.py +++ b/pype/modules/clockify/constants.py @@ -8,7 +8,7 @@ CLOCKIFY_FTRACK_SERVER_PATH = os.path.join( CLOCKIFY_FTRACK_USER_PATH = os.path.join( os.path.dirname(__file__), "ftrack", "user" ) -CREDENTIALS_PATH = os.path.normpath(os.path.join( +CREDENTIALS_JSON_PATH = os.path.normpath(os.path.join( appdirs.user_data_dir("pype-app", "pype"), "clockify.json" )) From 0de373f94fd19bcc0421edad3918862a1377d0ba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:59:46 +0200 Subject: [PATCH 115/947] formatting fix --- pype/modules/clockify/clockify.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 02b322c1c6..247bdaf0b1 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -195,9 +195,10 @@ class ClockifyModule: ).format(project_name)) msg = ( - "Project \"{}\" is not in Clockify Workspace \"{}\"." + "Project \"{}\" is not" + " in Clockify Workspace \"{}\"." "

Please inform your Project Manager." - ).format(project_name, str(self.clockapi.workspace)) + ).format(project_name, str(self.clockapi.workspace_name)) self.message_widget = MessageWidget( self.main_parent, msg, "Clockify - Info Message" From eeb5b54cef05c91cc610e68a0406d6f022fd3410 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:00:35 +0200 Subject: [PATCH 116/947] using constant in clockify module --- pype/modules/clockify/clockify.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 247bdaf0b1..5191727b33 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -3,11 +3,12 @@ import threading from pype.api import Logger from avalon import style from Qt import QtWidgets -from . import ClockifySettings, ClockifyAPI, MessageWidget +from . import ( + ClockifySettings, ClockifyAPI, MessageWidget, CLOCKIFY_FTRACK_USER_PATH +) class ClockifyModule: - workspace_name = None def __init__(self, main_parent=None, parent=None): @@ -50,14 +51,12 @@ class ClockifyModule: def process_modules(self, modules): if 'FtrackModule' in modules: - actions_path = os.path.sep.join([ - os.path.dirname(__file__), - 'ftrack_actions' - ]) current = os.environ.get('FTRACK_ACTIONS_PATH', '') if current: current += os.pathsep - os.environ['FTRACK_ACTIONS_PATH'] = current + actions_path + os.environ['FTRACK_ACTIONS_PATH'] = ( + current + CLOCKIFY_FTRACK_USER_PATH + ) if 'AvalonApps' in modules: from launcher import lib From 774f7953dd9b0e488f738b005370ba4334bc6b38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:00:57 +0200 Subject: [PATCH 117/947] clockify module can handle new clockify api --- pype/modules/clockify/clockify.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 5191727b33..4c99fb8a15 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -21,7 +21,7 @@ class ClockifyModule: self.main_parent = main_parent self.parent = parent - self.clockapi = ClockifyAPI() + self.clockapi = ClockifyAPI(master_parent=self) self.message_widget = None self.widget_settings = ClockifySettings(main_parent, self) self.widget_settings_required = None @@ -32,8 +32,6 @@ class ClockifyModule: self.bool_api_key_set = False self.bool_workspace_set = False self.bool_timer_run = False - - self.clockapi.set_master(self) self.bool_api_key_set = self.clockapi.set_api() def tray_start(self): From 489bed82a6b83bbc9ea7c6bf4631d974e894ca44 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:02:18 +0200 Subject: [PATCH 118/947] sync to clockify changed label --- pype/modules/clockify/ftrack/user/action_clockify_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync.py b/pype/modules/clockify/ftrack/user/action_clockify_sync.py index 0ba4c3a265..50ec455b13 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync.py @@ -9,7 +9,7 @@ class SyncClocify(BaseAction): #: Action identifier. identifier = 'clockify.sync' #: Action label. - label = 'Sync To Clockify' + label = 'Sync To Clockify (local)' #: Action description. description = 'Synchronise data to Clockify workspace' #: roles that are allowed to register this action From c840f31b598fbca6f0a6aa66499bb99316b3afa2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:06:12 +0200 Subject: [PATCH 119/947] renamed clockify action file name --- .../{action_clockify_sync.py => action_clockify_sync_local.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename pype/modules/clockify/ftrack/user/{action_clockify_sync.py => action_clockify_sync_local.py} (99%) diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync.py b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py similarity index 99% rename from pype/modules/clockify/ftrack/user/action_clockify_sync.py rename to pype/modules/clockify/ftrack/user/action_clockify_sync_local.py index 50ec455b13..a7385c4774 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -7,7 +7,7 @@ class SyncClocify(BaseAction): '''Synchronise project names and task types.''' #: Action identifier. - identifier = 'clockify.sync' + identifier = 'clockify.sync.local' #: Action label. label = 'Sync To Clockify (local)' #: Action description. From e97000b26d3a7a8c7c2fac022168b5cf03b87aa1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:06:39 +0200 Subject: [PATCH 120/947] renamed server action --- .../{action_clockify_sync.py => action_clockify_sync_server.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/modules/clockify/ftrack/server/{action_clockify_sync.py => action_clockify_sync_server.py} (100%) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py similarity index 100% rename from pype/modules/clockify/ftrack/server/action_clockify_sync.py rename to pype/modules/clockify/ftrack/server/action_clockify_sync_server.py From 789f965defd60fffa012b55d30ff73420a165de5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:07:07 +0200 Subject: [PATCH 121/947] initial modifications of clockify server action --- .../server/action_clockify_sync_server.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 0ba4c3a265..f09b1cc746 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,23 +1,16 @@ import json -from pype.modules.ftrack.lib import BaseAction, statics_icon +from pype.modules.ftrack.lib import BaseAction from pype.modules.clockify import ClockifyAPI -class SyncClocify(BaseAction): +class SyncClocifyServer(BaseAction): '''Synchronise project names and task types.''' - #: Action identifier. - identifier = 'clockify.sync' - #: Action label. - label = 'Sync To Clockify' - #: Action description. - description = 'Synchronise data to Clockify workspace' - #: roles that are allowed to register this action + identifier = "clockify.sync.server" + label = "Sync To Clockify (server)" + description = "Synchronise data to Clockify workspace" role_list = ["Pypeclub", "Administrator", "project Manager"] - #: icon - icon = statics_icon("app_icons", "clockify-white.png") - #: CLockifyApi clockapi = ClockifyAPI() def discover(self, session, entities, event): @@ -28,6 +21,18 @@ class SyncClocify(BaseAction): return True return False + def register(self): + self.session.event_hub.subscribe( + "topic=ftrack.action.discover", + self._discover, + priority=self.priority + ) + + launch_subscription = ( + "topic=ftrack.action.launch and data.actionIdentifier={}" + ).format(self.identifier) + self.session.event_hub.subscribe(launch_subscription, self._launch) + def launch(self, session, entities, event): self.clockapi.set_api() if self.clockapi.workspace_id is None: From e38de6ed581d77f89b4242c0a5b30e63a1bb943e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:09:24 +0200 Subject: [PATCH 122/947] allow discovering only for discover roles --- .../server/action_clockify_sync_server.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index f09b1cc746..19deb05e6d 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -9,16 +9,30 @@ class SyncClocifyServer(BaseAction): identifier = "clockify.sync.server" label = "Sync To Clockify (server)" description = "Synchronise data to Clockify workspace" - role_list = ["Pypeclub", "Administrator", "project Manager"] + + discover_role_list = ["Pypeclub", "Administrator", "project Manager"] clockapi = ClockifyAPI() def discover(self, session, entities, event): if ( - len(entities) == 1 - and entities[0].entity_type.lower() == "project" + len(entities) != 1 + or entities[0].entity_type.lower() == "project" ): - return True + return False + + # Get user and check his roles + user_id = event.get("source", {}).get("user", {}).get("id") + if not user_id: + return False + + user = session.query("User where id is \"{}\"".format(user_id)).first() + if not user: + return False + + for role in user["user_security_roles"]: + if role["security_role"]["name"] in self.discover_role_list: + return True return False def register(self): @@ -124,4 +138,4 @@ class SyncClocifyServer(BaseAction): def register(session, **kw): - SyncClocify(session).register() + SyncClocifyServer(session).register() From de9a075f04b24afea517daeaf4486a68e855fc5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:25:59 +0200 Subject: [PATCH 123/947] modified headers access in clockify --- pype/modules/clockify/clockify_api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 8ebed302fe..a8eefe13a4 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -11,7 +11,11 @@ class ClockifyAPI: self.workspace_name = workspace_name self.master_parent = master_parent self.workspace_id = None - self.headers = {"X-Api-Key": api_key} + self.api_key = api_key + + @property + def headers(self): + return {"X-Api-Key": self.api_key} def verify_api(self): for key, value in self.headers.items(): @@ -24,7 +28,7 @@ class ClockifyAPI: api_key = self.get_api_key() if api_key is not None and self.validate_api_key(api_key) is True: - self.headers["X-Api-Key"] = api_key + self.api_key = api_key self.set_workspace() if self.master_parent: self.master_parent.signed_in() From 8dfca2378c9a0653c76f4713f50b18943822647d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:26:13 +0200 Subject: [PATCH 124/947] minor tweaks --- .../server/action_clockify_sync_server.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 19deb05e6d..7e3c266b72 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -62,15 +62,13 @@ class SyncClocifyServer(BaseAction): } # JOB SETTINGS - userId = event['source']['user']['id'] - user = session.query('User where id is ' + userId).one() + user_id = event["source"]["user"]["id"] + user = session.query("User where id is " + user_id).one() - job = session.create('Job', { - 'user': user, - 'status': 'running', - 'data': json.dumps({ - 'description': 'Sync Ftrack to Clockify' - }) + job = session.create("Job", { + "user": user, + "status": "running", + "data": json.dumps({"description": "Sync Ftrack to Clockify"}) }) session.commit() @@ -127,7 +125,10 @@ class SyncClocifyServer(BaseAction): job["status"] = "done" except Exception: - pass + self.log.warning( + "Synchronization to clockify failed.", + exc_info=True + ) finally: if job["status"] != "done": From 4f244d187b75677435161aa6790ef4542a4a7df0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:48:34 +0200 Subject: [PATCH 125/947] clockify api is set in initialization part of action on server --- .../server/action_clockify_sync_server.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 7e3c266b72..0a844c9ef5 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,3 +1,4 @@ +import os import json from pype.modules.ftrack.lib import BaseAction from pype.modules.clockify import ClockifyAPI @@ -12,7 +13,32 @@ class SyncClocifyServer(BaseAction): discover_role_list = ["Pypeclub", "Administrator", "project Manager"] - clockapi = ClockifyAPI() + def __init__(self, *args, **kwargs): + super(SyncClocifyServer, self).__init__(*args, **kwargs) + + self.workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + api_key = os.environ.get("CLOCKIFY_API_KEY") + self.clockapi = ClockifyAPI(self.workspace_name, api_key) + self.api_key = api_key + + if api_key is None: + modified_key = "None" + else: + str_len = int(len(api_key) / 2) + start_replace = int(len(api_key) / 4) + modified_key = "" + for idx in range(len(api_key)): + if idx >= start_replace and idx < start_replace + str_len: + replacement = "X" + else: + replacement = api_key[idx] + modified_key += replacement + + self.log.info( + "Clockify info. Workspace: \"{}\" API key: \"{}\"".format( + str(self.workspace_name), str(modified_key) + ) + ) def discover(self, session, entities, event): if ( @@ -48,7 +74,6 @@ class SyncClocifyServer(BaseAction): self.session.event_hub.subscribe(launch_subscription, self._launch) def launch(self, session, entities, event): - self.clockapi.set_api() if self.clockapi.workspace_id is None: return { "success": False, From eb30a53af8c6327a9f3d5d2f642a74032527147a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:49:12 +0200 Subject: [PATCH 126/947] changed class name --- .../clockify/ftrack/user/action_clockify_sync_local.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py index a7385c4774..528614eeba 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -3,7 +3,7 @@ from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.modules.clockify import ClockifyAPI -class SyncClocify(BaseAction): +class SyncClocifyLocal(BaseAction): '''Synchronise project names and task types.''' #: Action identifier. @@ -119,4 +119,4 @@ class SyncClocify(BaseAction): def register(session, **kw): - SyncClocify(session).register() + SyncClocifyLocal(session).register() From 98c1587da7ad6f26d517526e7c8f534d9f35f77e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:02:50 +0200 Subject: [PATCH 127/947] added clockify registration to ftrack event processor --- .../ftrack_server/sub_event_processor.py | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index d7bb7a53b3..de0f0459a9 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -9,7 +9,7 @@ from pype.modules.ftrack.ftrack_server.lib import ( SocketSession, ProcessEventHub, TOPIC_STATUS_SERVER ) import ftrack_api -from pype.api import Logger +from pype.api import Logger, config log = Logger().get_logger("Event processor") @@ -55,6 +55,42 @@ def register(session): ) +def clockify_module_registration(): + module_name = "Clockify" + + menu_items = config.get_presets()["tray"]["menu_items"] + if not menu_items["item_usage"][module_name]: + return + + api_key = os.environ.get("CLOCKIFY_API_KEY") + if not api_key: + log.warning("Clockify API key is not set.") + return + + workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + if not workspace_name: + workspace_name = ( + menu_items + .get("attributes", {}) + .get(module_name, {}) + .get("workspace_name", {}) + ) + + if not workspace_name: + log.warning("Clockify Workspace is not set.") + return + + os.environ["CLOCKIFY_WORKSPACE"] = workspace_name + + from pype.modules.clockify import CLOCKIFY_FTRACK_SERVER_PATH + + current = os.environ.get("FTRACK_EVENTS_PATH") or "" + if current: + current += os.pathsep + os.environ["FTRACK_EVENTS_PATH"] = current + CLOCKIFY_FTRACK_SERVER_PATH + return True + + def main(args): port = int(args[-1]) # Create a TCP/IP socket @@ -66,6 +102,11 @@ def main(args): sock.connect(server_address) sock.sendall(b"CreatedProcess") + try: + clockify_result = clockify_module_registration() + except Exception: + log.info("Clockify registration failed.", exc_info=True) + try: session = SocketSession( auto_connect_event_hub=True, sock=sock, Eventhub=ProcessEventHub From eb4153a7865a7ebe941c2a3f076cde680e05c9fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:08:03 +0200 Subject: [PATCH 128/947] added clockifyapikey and clockifyworkspace arguments to ftrack event server script --- .../ftrack/ftrack_server/event_server_cli.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index 73c7abfc5d..bf51c37290 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -522,6 +522,21 @@ def main(argv): help="Load creadentials from apps dir", action="store_true" ) + parser.add_argument( + "-clockifyapikey", type=str, + help=( + "Enter API key for Clockify actions." + " (default from environment: $CLOCKIFY_API_KEY)" + ) + ) + parser.add_argument( + "-clockifyworkspace", type=str, + help=( + "Enter workspace for Clockify." + " (default from module presets or " + "environment: $CLOCKIFY_WORKSPACE)" + ) + ) ftrack_url = os.environ.get('FTRACK_SERVER') username = os.environ.get('FTRACK_API_USER') api_key = os.environ.get('FTRACK_API_KEY') @@ -546,6 +561,12 @@ def main(argv): if kwargs.ftrackapikey: api_key = kwargs.ftrackapikey + if kwargs.clockifyworkspace: + os.environ["CLOCKIFY_WORKSPACE"] = kwargs.clockifyworkspace + + if kwargs.clockifyapikey: + os.environ["CLOCKIFY_API_KEY"] = kwargs.clockifyapikey + legacy = kwargs.legacy # Check url regex and accessibility ftrack_url = check_ftrack_url(ftrack_url) From 360264117ee62d92cd58da90fcc9f61567ec0885 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 22 Jul 2020 17:07:07 +0100 Subject: [PATCH 129/947] Adding "audioMain" to Nuke reviews. - also added get_latest_version method to library. --- pype/api.py | 6 ++-- pype/lib.py | 37 +++++++++++++++++++++ pype/plugins/nuke/publish/collect_review.py | 18 ++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/pype/api.py b/pype/api.py index 44a31f2626..ce13688d13 100644 --- a/pype/api.py +++ b/pype/api.py @@ -40,7 +40,8 @@ from .lib import ( get_version_from_path, get_last_version_from_path, modified_environ, - add_tool_to_environment + add_tool_to_environment, + get_latest_version ) # Special naming case for subprocess since its a built-in method. @@ -85,5 +86,6 @@ __all__ = [ "modified_environ", "add_tool_to_environment", - "subprocess" + "subprocess", + "get_latest_version" ] diff --git a/pype/lib.py b/pype/lib.py index 87808e53f5..29e02ffd57 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -1387,3 +1387,40 @@ def ffprobe_streams(path_to_file): popen_output = popen.communicate()[0] log.debug("FFprobe output: {}".format(popen_output)) return json.loads(popen_output)["streams"] + + +def get_latest_version(asset_name, subset_name): + """Retrieve latest version from `asset_name`, and `subset_name`. + + Args: + asset_name (str): Name of asset. + subset_name (str): Name of subset. + """ + # Get asset + asset_name = io.find_one( + {"type": "asset", "name": asset_name}, projection={"name": True} + ) + + subset = io.find_one( + {"type": "subset", "name": subset_name, "parent": asset_name["_id"]}, + projection={"_id": True, "name": True}, + ) + + # Check if subsets actually exists. + assert subset, "No subsets found." + + # Get version + version_projection = { + "name": True, + "parent": True, + } + + version = io.find_one( + {"type": "version", "parent": subset["_id"]}, + projection=version_projection, + sort=[("name", -1)], + ) + + assert version, "No version found, this is a bug" + + return version diff --git a/pype/plugins/nuke/publish/collect_review.py b/pype/plugins/nuke/publish/collect_review.py index c95c94541d..e7e8da19a1 100644 --- a/pype/plugins/nuke/publish/collect_review.py +++ b/pype/plugins/nuke/publish/collect_review.py @@ -1,4 +1,7 @@ import pyblish.api +import pype.api +from avalon import io, api + import nuke @@ -23,6 +26,21 @@ class CollectReview(pyblish.api.InstancePlugin): if not node["review"].value(): return + # Add audio to instance if it exists. + try: + version = pype.api.get_latest_version( + instance.context.data["assetEntity"]["name"], "audioMain" + ) + representation = io.find_one( + {"type": "representation", "parent": version["_id"]} + ) + instance.data["audio"] = [{ + "offset": 0, + "filename": api.get_representation_path(representation) + }] + except AssertionError: + pass + instance.data["families"].append("review") instance.data['families'].append('ftrack') From ec9ff173379cf6601414445bac41b621272a4a16 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:32:45 +0200 Subject: [PATCH 130/947] removed workspace_name from clockify api init --- pype/modules/clockify/clockify_api.py | 8 ++++---- .../ftrack/server/action_clockify_sync_server.py | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index a8eefe13a4..ca642a1c21 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -7,10 +7,10 @@ from . import CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH class ClockifyAPI: - def __init__(self, workspace_name=None, api_key=None, master_parent=None): - self.workspace_name = workspace_name - self.master_parent = master_parent + def __init__(self, api_key=None, master_parent=None): + self.workspace_name = None self.workspace_id = None + self.master_parent = master_parent self.api_key = api_key @property @@ -73,7 +73,7 @@ class ClockifyAPI: ) # this regex is neccessary: UNICODE strings are crashing # during json serialization - id_regex ='\"{1}id\"{1}\:{1}\"{1}\w+\"{1}' + id_regex = '\"{1}id\"{1}\:{1}\"{1}\w+\"{1}' result = re.findall(id_regex, str(response.content)) if len(result) != 1: # replace with log and better message? diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 0a844c9ef5..442d1f92df 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -16,11 +16,10 @@ class SyncClocifyServer(BaseAction): def __init__(self, *args, **kwargs): super(SyncClocifyServer, self).__init__(*args, **kwargs) - self.workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") api_key = os.environ.get("CLOCKIFY_API_KEY") - self.clockapi = ClockifyAPI(self.workspace_name, api_key) - self.api_key = api_key - + self.clockapi = ClockifyAPI(api_key) + self.clockapi.set_workspace(workspace_name) if api_key is None: modified_key = "None" else: @@ -36,7 +35,7 @@ class SyncClocifyServer(BaseAction): self.log.info( "Clockify info. Workspace: \"{}\" API key: \"{}\"".format( - str(self.workspace_name), str(modified_key) + str(workspace_name), str(modified_key) ) ) From 2cfe2e139307236af803428ae33cffe87d6f5b0e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:32:58 +0200 Subject: [PATCH 131/947] fix discover condition --- .../clockify/ftrack/server/action_clockify_sync_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 442d1f92df..94b2f21da4 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -42,7 +42,7 @@ class SyncClocifyServer(BaseAction): def discover(self, session, entities, event): if ( len(entities) != 1 - or entities[0].entity_type.lower() == "project" + or entities[0].entity_type.lower() != "project" ): return False From 0318a5c35cf4829843dbfee5acd13428fab51aba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:44:18 +0200 Subject: [PATCH 132/947] removed unused variable --- pype/modules/ftrack/ftrack_server/sub_event_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index de0f0459a9..baef2ec5f6 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -103,7 +103,7 @@ def main(args): sock.sendall(b"CreatedProcess") try: - clockify_result = clockify_module_registration() + clockify_module_registration() except Exception: log.info("Clockify registration failed.", exc_info=True) From f7740839b7806a4c0979a9378349b746392796e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:54:33 +0200 Subject: [PATCH 133/947] removed kredenc part from code --- pype/hosts/maya/customize.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index cf179e1d63..c929de22b8 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -145,16 +145,6 @@ def override_toolbox_ui(): parent=parent) controls.append(control) - # control = mc.iconTextButton( - # "pype_toolbox", - # annotation="Kredenc", - # label="Kredenc", - # image=os.path.join(icons, "kredenc_logo.png"), - # bgc=background_color, - # width=icon_size, - # height=icon_size, - # parent=parent) - # controls.append(control) # Add the buttons on the bottom and stack # them above each other with side padding From f7aa0a83da9994971deb533ff86e2807c58d00cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:56:20 +0200 Subject: [PATCH 134/947] all tools are tried to import --- pype/hosts/maya/customize.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index c929de22b8..bd0901a6fd 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -69,11 +69,30 @@ def override_component_mask_commands(): def override_toolbox_ui(): """Add custom buttons in Toolbox as replacement for Maya web help icon.""" + inventory = None + loader = None + launch_workfiles_app = None + mayalookassigner = None + try: + import avalon.tools.sceneinventory as inventory + except Exception: + log.warning("Could not import SceneInventory tool") + + try: + import avalon.tools.loader as loader + except Exception: + log.warning("Could not import Loader tool") + + try: + from avalon.maya.pipeline import launch_workfiles_app + except Exception: + log.warning("Could not import Workfiles tool") + + try: + import mayalookassigner + except Exception: + log.warning("Could not import Maya Look assigner tool") - import avalon.tools.sceneinventory as inventory - import avalon.tools.loader as loader - from avalon.maya.pipeline import launch_workfiles_app - import mayalookassigner from pype.api import resources icons = resources.get_resource("icons") From af26c083e549e5e5851b1415aed668bcfffc69ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:56:41 +0200 Subject: [PATCH 135/947] only imported tools are added to controls --- pype/hosts/maya/customize.py | 102 +++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index bd0901a6fd..c6b5ac63cf 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -115,55 +115,65 @@ def override_toolbox_ui(): # Create our controls background_color = (0.267, 0.267, 0.267) controls = [] + if mayalookassigner: + controls.append( + mc.iconTextButton( + "pype_toolbox_lookmanager", + annotation="Look Manager", + label="Look Manager", + image=os.path.join(icons, "lookmanager.png"), + command=lambda: mayalookassigner.show(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) - control = mc.iconTextButton( - "pype_toolbox_lookmanager", - annotation="Look Manager", - label="Look Manager", - image=os.path.join(icons, "lookmanager.png"), - command=lambda: mayalookassigner.show(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) + if launch_workfiles_app: + controls.append( + mc.iconTextButton( + "pype_toolbox_workfiles", + annotation="Work Files", + label="Work Files", + image=os.path.join(icons, "workfiles.png"), + command=lambda: launch_workfiles_app(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) - control = mc.iconTextButton( - "pype_toolbox_workfiles", - annotation="Work Files", - label="Work Files", - image=os.path.join(icons, "workfiles.png"), - command=lambda: launch_workfiles_app(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) - - control = mc.iconTextButton( - "pype_toolbox_loader", - annotation="Loader", - label="Loader", - image=os.path.join(icons, "loader.png"), - command=lambda: loader.show(use_context=True), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) - - control = mc.iconTextButton( - "pype_toolbox_manager", - annotation="Inventory", - label="Inventory", - image=os.path.join(icons, "inventory.png"), - command=lambda: inventory.show(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) + if loader: + controls.append( + mc.iconTextButton( + "pype_toolbox_loader", + annotation="Loader", + label="Loader", + image=os.path.join(icons, "loader.png"), + command=lambda: loader.show(use_context=True), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) + if inventory: + controls.append( + mc.iconTextButton( + "pype_toolbox_manager", + annotation="Inventory", + label="Inventory", + image=os.path.join(icons, "inventory.png"), + command=lambda: inventory.show(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) # Add the buttons on the bottom and stack # them above each other with side padding From 710fdd4d5b94224b500ce6b3158aa68c154d2713 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:57:06 +0200 Subject: [PATCH 136/947] skip adding tools process if none of tools import was succesful --- pype/hosts/maya/customize.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index c6b5ac63cf..ee3ad4f239 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -97,6 +97,11 @@ def override_toolbox_ui(): icons = resources.get_resource("icons") + if not any(( + mayalookassigner, launch_workfiles_app, loader, inventory + )): + return + # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" if not mc.iconTextButton(web_button, query=True, exists=True): From b6b3ea0f35836d364ed186cf8faf82f08247d692 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 19:56:09 +0200 Subject: [PATCH 137/947] cleaned clockify imports --- pype/modules/clockify/__init__.py | 10 ---------- pype/modules/clockify/clockify.py | 6 +++--- pype/modules/clockify/clockify_api.py | 4 +++- .../ftrack/server/action_clockify_sync_server.py | 2 +- .../clockify/ftrack/user/action_clockify_sync_local.py | 2 +- .../modules/clockify/launcher_actions/ClockifyStart.py | 2 +- pype/modules/clockify/launcher_actions/ClockifySync.py | 2 +- .../ftrack/ftrack_server/sub_event_processor.py | 2 +- 8 files changed, 11 insertions(+), 19 deletions(-) diff --git a/pype/modules/clockify/__init__.py b/pype/modules/clockify/__init__.py index 0ea58b5b00..8e11d2f5f4 100644 --- a/pype/modules/clockify/__init__.py +++ b/pype/modules/clockify/__init__.py @@ -1,13 +1,3 @@ -from .constants import ( - CLOCKIFY_ENDPOINT, - ADMIN_PERMISSION_NAMES, - CREDENTIALS_JSON_PATH, - CLOCKIFY_FTRACK_USER_PATH, - CLOCKIFY_FTRACK_SERVER_PATH -) -from .clockify_api import ClockifyAPI -from .widget_settings import ClockifySettings -from .widget_message import MessageWidget from .clockify import ClockifyModule CLASS_DEFINIION = ClockifyModule diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 4c99fb8a15..fea15a1bea 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -3,9 +3,9 @@ import threading from pype.api import Logger from avalon import style from Qt import QtWidgets -from . import ( - ClockifySettings, ClockifyAPI, MessageWidget, CLOCKIFY_FTRACK_USER_PATH -) +from .widgets import ClockifySettings, MessageWidget +from .clockify_api import ClockifyAPI +from .constants import CLOCKIFY_FTRACK_USER_PATH class ClockifyModule: diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index ca642a1c21..0a09c65628 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -3,7 +3,9 @@ import re import requests import json import datetime -from . import CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH +from .constants import ( + CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH +) class ClockifyAPI: diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 94b2f21da4..ae911f6258 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,7 +1,7 @@ import os import json from pype.modules.ftrack.lib import BaseAction -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI class SyncClocifyServer(BaseAction): diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py index 528614eeba..e74bf3dbb3 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -1,6 +1,6 @@ import json from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI class SyncClocifyLocal(BaseAction): diff --git a/pype/modules/clockify/launcher_actions/ClockifyStart.py b/pype/modules/clockify/launcher_actions/ClockifyStart.py index d5e9164977..f97360662f 100644 --- a/pype/modules/clockify/launcher_actions/ClockifyStart.py +++ b/pype/modules/clockify/launcher_actions/ClockifyStart.py @@ -1,6 +1,6 @@ from avalon import api, io from pype.api import Logger -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI log = Logger().get_logger(__name__, "clockify_start") diff --git a/pype/modules/clockify/launcher_actions/ClockifySync.py b/pype/modules/clockify/launcher_actions/ClockifySync.py index 0f20d1dce1..a77c038076 100644 --- a/pype/modules/clockify/launcher_actions/ClockifySync.py +++ b/pype/modules/clockify/launcher_actions/ClockifySync.py @@ -1,5 +1,5 @@ from avalon import api, io -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI from pype.api import Logger log = Logger().get_logger(__name__, "clockify_sync") diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index baef2ec5f6..4a3241dd4f 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -82,7 +82,7 @@ def clockify_module_registration(): os.environ["CLOCKIFY_WORKSPACE"] = workspace_name - from pype.modules.clockify import CLOCKIFY_FTRACK_SERVER_PATH + from pype.modules.clockify.constants import CLOCKIFY_FTRACK_SERVER_PATH current = os.environ.get("FTRACK_EVENTS_PATH") or "" if current: From 3b0fae9fa25055e73d99086d4348d6f9d5e42b86 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 19:56:21 +0200 Subject: [PATCH 138/947] clockify widgets are in one file --- pype/modules/clockify/widget_message.py | 92 ------------------- .../{widget_settings.py => widgets.py} | 90 +++++++++++++++++- 2 files changed, 89 insertions(+), 93 deletions(-) delete mode 100644 pype/modules/clockify/widget_message.py rename pype/modules/clockify/{widget_settings.py => widgets.py} (66%) diff --git a/pype/modules/clockify/widget_message.py b/pype/modules/clockify/widget_message.py deleted file mode 100644 index 9e4fad1df1..0000000000 --- a/pype/modules/clockify/widget_message.py +++ /dev/null @@ -1,92 +0,0 @@ -from Qt import QtCore, QtGui, QtWidgets -from avalon import style -from pype.api import resources - - -class MessageWidget(QtWidgets.QWidget): - - SIZE_W = 300 - SIZE_H = 130 - - closed = QtCore.Signal() - - def __init__(self, parent=None, messages=[], title="Message"): - - super(MessageWidget, self).__init__() - - self._parent = parent - - # Icon - if parent and hasattr(parent, 'icon'): - self.setWindowIcon(parent.icon) - else: - icon = QtGui.QIcon(resources.pype_icon_filepath()) - self.setWindowIcon(icon) - - self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint | - QtCore.Qt.WindowMinimizeButtonHint - ) - - # Font - self.font = QtGui.QFont() - self.font.setFamily("DejaVu Sans Condensed") - self.font.setPointSize(9) - self.font.setBold(True) - self.font.setWeight(50) - self.font.setKerning(True) - - # Size setting - self.resize(self.SIZE_W, self.SIZE_H) - self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) - self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) - - # Style - self.setStyleSheet(style.load_stylesheet()) - - self.setLayout(self._ui_layout(messages)) - self.setWindowTitle(title) - - def _ui_layout(self, messages): - if not messages: - messages = ["*Misssing messages (This is a bug)*", ] - - elif not isinstance(messages, (tuple, list)): - messages = [messages, ] - - main_layout = QtWidgets.QVBoxLayout(self) - - labels = [] - for message in messages: - label = QtWidgets.QLabel(message) - label.setFont(self.font) - label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - label.setTextFormat(QtCore.Qt.RichText) - label.setWordWrap(True) - - labels.append(label) - main_layout.addWidget(label) - - btn_close = QtWidgets.QPushButton("Close") - btn_close.setToolTip('Close this window') - btn_close.clicked.connect(self.on_close_clicked) - - btn_group = QtWidgets.QHBoxLayout() - btn_group.addStretch(1) - btn_group.addWidget(btn_close) - - main_layout.addLayout(btn_group) - - self.labels = labels - self.btn_group = btn_group - self.btn_close = btn_close - self.main_layout = main_layout - - return main_layout - - def on_close_clicked(self): - self.close() - - def close(self, *args, **kwargs): - self.closed.emit() - super(MessageWidget, self).close(*args, **kwargs) diff --git a/pype/modules/clockify/widget_settings.py b/pype/modules/clockify/widgets.py similarity index 66% rename from pype/modules/clockify/widget_settings.py rename to pype/modules/clockify/widgets.py index 7e5ee300bb..dc57a48ecb 100644 --- a/pype/modules/clockify/widget_settings.py +++ b/pype/modules/clockify/widgets.py @@ -1,9 +1,97 @@ -import os from Qt import QtCore, QtGui, QtWidgets from avalon import style from pype.api import resources +class MessageWidget(QtWidgets.QWidget): + + SIZE_W = 300 + SIZE_H = 130 + + closed = QtCore.Signal() + + def __init__(self, parent=None, messages=[], title="Message"): + + super(MessageWidget, self).__init__() + + self._parent = parent + + # Icon + if parent and hasattr(parent, 'icon'): + self.setWindowIcon(parent.icon) + else: + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) + + self.setWindowFlags( + QtCore.Qt.WindowCloseButtonHint | + QtCore.Qt.WindowMinimizeButtonHint + ) + + # Font + self.font = QtGui.QFont() + self.font.setFamily("DejaVu Sans Condensed") + self.font.setPointSize(9) + self.font.setBold(True) + self.font.setWeight(50) + self.font.setKerning(True) + + # Size setting + self.resize(self.SIZE_W, self.SIZE_H) + self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) + self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) + + # Style + self.setStyleSheet(style.load_stylesheet()) + + self.setLayout(self._ui_layout(messages)) + self.setWindowTitle(title) + + def _ui_layout(self, messages): + if not messages: + messages = ["*Misssing messages (This is a bug)*", ] + + elif not isinstance(messages, (tuple, list)): + messages = [messages, ] + + main_layout = QtWidgets.QVBoxLayout(self) + + labels = [] + for message in messages: + label = QtWidgets.QLabel(message) + label.setFont(self.font) + label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + label.setTextFormat(QtCore.Qt.RichText) + label.setWordWrap(True) + + labels.append(label) + main_layout.addWidget(label) + + btn_close = QtWidgets.QPushButton("Close") + btn_close.setToolTip('Close this window') + btn_close.clicked.connect(self.on_close_clicked) + + btn_group = QtWidgets.QHBoxLayout() + btn_group.addStretch(1) + btn_group.addWidget(btn_close) + + main_layout.addLayout(btn_group) + + self.labels = labels + self.btn_group = btn_group + self.btn_close = btn_close + self.main_layout = main_layout + + return main_layout + + def on_close_clicked(self): + self.close() + + def close(self, *args, **kwargs): + self.closed.emit() + super(MessageWidget, self).close(*args, **kwargs) + + class ClockifySettings(QtWidgets.QWidget): SIZE_W = 300 From 2909b7453e2117282d7181a84fb850824bf86b5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 11:03:44 +0200 Subject: [PATCH 139/947] initial commit --- .../kuba_each_case/global/creator.json | 8 + .../kuba_each_case/global/intents.json | 3 + .../project_presets/ftrack/ftrack_config.json | 11 + .../project_presets/global/creator.json | 8 + .../global/slates/example_HD.json | 212 +++ .../config/project_presets/maya/capture.json | 108 ++ .../project_presets/plugins/config.json | 1 + .../plugins/ftrack/publish.json | 6 + .../plugins/global/create.json | 1 + .../plugins/global/filter.json | 1 + .../project_presets/plugins/global/load.json | 1 + .../plugins/global/publish.json | 73 + .../project_presets/plugins/maya/create.json | 1 + .../project_presets/plugins/maya/filter.json | 9 + .../project_presets/plugins/maya/load.json | 18 + .../project_presets/plugins/maya/publish.json | 17 + .../plugins/maya/workfile_build.json | 54 + .../project_presets/plugins/nuke/create.json | 8 + .../project_presets/plugins/nuke/load.json | 1 + .../project_presets/plugins/nuke/publish.json | 48 + .../plugins/nuke/workfile_build.json | 11 + .../plugins/nukestudio/filter.json | 10 + .../plugins/nukestudio/publish.json | 8 + .../plugins/standalonepublisher/publish.json | 17 + .../project_presets/plugins/test/create.json | 8 + .../project_presets/plugins/test/publish.json | 10 + .../premiere/asset_default.json | 5 + .../project_presets/premiere/rules_tasks.json | 21 + .../project_presets/unreal/project_setup.json | 4 + .../studio_presets/ftrack/server_plugins.json | 1 + .../studio_presets/ftrack/user_plugins.json | 5 + .../studio_presets/global/applications.json | 39 + .../global/es/applications.json | 39 + .../config/studio_presets/global/intents.json | 9 + .../studio_presets/global/tray_items.json | 25 + .../muster/templates_mapping.json | 19 + .../applications_gui_schema.json | 153 ++ .../ftrack_projects_gui_schema.json | 30 + .../config_gui_schema/project_gui_schema.json | 13 + .../config_gui_schema/studio_gui_schema.json | 23 + .../config_gui_schema/tools_gui_schema.json | 29 + pype/tools/config_setting/interface.py | 49 + pype/tools/config_setting/style/__init__.py | 12 + pype/tools/config_setting/style/pype_icon.png | Bin 0 -> 3793 bytes pype/tools/config_setting/style/style.css | 90 ++ pype/tools/config_setting/widgets/__init__.py | 6 + pype/tools/config_setting/widgets/base.py | 282 ++++ pype/tools/config_setting/widgets/config.py | 236 +++ pype/tools/config_setting/widgets/inputs.py | 1346 +++++++++++++++++ pype/tools/config_setting/widgets/lib.py | 44 + pype/tools/config_setting/widgets/main.py | 26 + pype/tools/config_setting/widgets/tests.py | 127 ++ 52 files changed, 3286 insertions(+) create mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json create mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json create mode 100644 pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json create mode 100644 pype/tools/config_setting/config/project_presets/global/creator.json create mode 100644 pype/tools/config_setting/config/project_presets/global/slates/example_HD.json create mode 100644 pype/tools/config_setting/config/project_presets/maya/capture.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/config.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/filter.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/load.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/filter.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/load.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/load.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/test/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/test/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/premiere/asset_default.json create mode 100644 pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json create mode 100644 pype/tools/config_setting/config/project_presets/unreal/project_setup.json create mode 100644 pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json create mode 100644 pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/applications.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/es/applications.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/intents.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/tray_items.json create mode 100644 pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json create mode 100644 pype/tools/config_setting/config_gui_schema/applications_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/project_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/studio_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/tools_gui_schema.json create mode 100644 pype/tools/config_setting/interface.py create mode 100644 pype/tools/config_setting/style/__init__.py create mode 100644 pype/tools/config_setting/style/pype_icon.png create mode 100644 pype/tools/config_setting/style/style.css create mode 100644 pype/tools/config_setting/widgets/__init__.py create mode 100644 pype/tools/config_setting/widgets/base.py create mode 100644 pype/tools/config_setting/widgets/config.py create mode 100644 pype/tools/config_setting/widgets/inputs.py create mode 100644 pype/tools/config_setting/widgets/lib.py create mode 100644 pype/tools/config_setting/widgets/main.py create mode 100644 pype/tools/config_setting/widgets/tests.py diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json new file mode 100644 index 0000000000..d14e779f01 --- /dev/null +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json @@ -0,0 +1,8 @@ +{ + "Model": ["model"], + "Render Globals": ["light", "render"], + "Layout": ["layout"], + "Set Dress": ["setdress"], + "Look": ["look"], + "Rig": ["rigging"] +} diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json new file mode 100644 index 0000000000..bf147c7a19 --- /dev/null +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json @@ -0,0 +1,3 @@ +{ + "default": "test" +} diff --git a/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json b/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json new file mode 100644 index 0000000000..c9dbde4596 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json @@ -0,0 +1,11 @@ +{ + "status_update": { + "_ignore_": ["in progress", "ommited", "on hold"], + "Ready": ["not ready"], + "In Progress" : ["_any_"] + }, + "status_version_to_task": { + "in progress": "in progress", + "approved": "approved" + } +} diff --git a/pype/tools/config_setting/config/project_presets/global/creator.json b/pype/tools/config_setting/config/project_presets/global/creator.json new file mode 100644 index 0000000000..d14e779f01 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/global/creator.json @@ -0,0 +1,8 @@ +{ + "Model": ["model"], + "Render Globals": ["light", "render"], + "Layout": ["layout"], + "Set Dress": ["setdress"], + "Look": ["look"], + "Rig": ["rigging"] +} diff --git a/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json b/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json new file mode 100644 index 0000000000..b06391fb63 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json @@ -0,0 +1,212 @@ +{ + "width": 1920, + "height": 1080, + "destination_path": "{destination_path}", + "style": { + "*": { + "font-family": "arial", + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top" + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": true + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "font-bold": true, + "ellide": false, + "word-wrap": true, + "max-lines": null + }, + "table-item-col[1]": { + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" + } + }, + "items": [{ + "type": "layer", + "direction": 1, + "name": "MainLayer", + "style": { + "#MainLayer": { + "width": 1094, + "height": 1000, + "margin": 25, + "padding": 0 + }, + "#LeftSide": { + "margin-right": 25 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "items": [{ + "type": "layer", + "direction": 1, + "style": { + "table-item": { + "bg-color": "transparent", + "padding-bottom": 20 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": true, + "font-size": 40 + } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "{project[name]}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[0:1]": { + "width": 580 + } + } + }, { + "type": "table", + "values": [ + ["Submitting For:", "{intent}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[0:1]": { + "width": 218, + "alignment-horizontal": "right" + } + } + }] + }, { + "type": "rectangle", + "style": { + "bg-color": "#bc1015", + "width": 1108, + "height": 5, + "fill": true + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Version name:", "{version_name}"], + ["Date:", "{date}"], + ["Shot Types:", "{shot_type}"], + ["Submission Note:", "{submission_note}"] + ], + "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[0:1]": { + "font-bold": true + }, + "table-item-field[3:0]": { + "word-wrap": true, + "ellide": true, + "max-lines": 4 + }, + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 150 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 958 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "items": [{ + "type": "placeholder", + "name": "thumbnail", + "path": "{thumbnail_path}", + "style": { + "width": 730, + "height": 412 + } + }, { + "type": "placeholder", + "name": "colorbar", + "path": "{color_bar_path}", + "return_data": true, + "style": { + "width": 730, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Vendor:", "{vendor}"], + ["Shot Name:", "{shot_name}"], + ["Frames:", "{frame_start} - {frame_end} ({duration})"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left", + "width": 200 + }, + "table-item-col[1]": { + "alignment-horizontal": "right", + "width": 530, + "font-size": 30 + } + } + }] + }] + }] +} diff --git a/pype/tools/config_setting/config/project_presets/maya/capture.json b/pype/tools/config_setting/config/project_presets/maya/capture.json new file mode 100644 index 0000000000..b6c4893034 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/maya/capture.json @@ -0,0 +1,108 @@ +{ + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "backgroundBottom": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "backgroundTop": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true + }, + "IO": { + "name": "", + "open_finished": false, + "raw_frame_numbers": false, + "recent_playblasts": [], + "save_file": false + }, + "PanZoom": { + "pan_zoom": true + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "height": 1080, + "mode": "Custom", + "percent": 1.0, + "width": 1920 + }, + "Time Range": { + "end_frame": 25, + "frame": "", + "start_frame": 0, + "time": "Time Slider" + }, + "Viewport Options": { + "cameras": false, + "clipGhosts": false, + "controlVertices": false, + "deformers": false, + "dimensions": false, + "displayLights": 0, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "gpuCacheDisplayFilter": false, + "greasePencils": false, + "grid": false, + "hairSystems": false, + "handles": false, + "high_quality": true, + "hud": false, + "hulls": false, + "ikHandles": false, + "imagePlane": false, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "nurbsCurves": false, + "nurbsSurfaces": false, + "override_viewport_options": true, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": true, + "shadows": false, + "strokes": false, + "subdivSurfaces": false, + "textures": false, + "twoSidedLighting": true + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/config.json b/pype/tools/config_setting/config/project_presets/plugins/config.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json b/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json new file mode 100644 index 0000000000..d0469ae4f7 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json @@ -0,0 +1,6 @@ +{ + "IntegrateFtrackNote": { + "note_with_intent_template": "{intent}: {comment}", + "note_labels": [] + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/create.json b/pype/tools/config_setting/config/project_presets/plugins/global/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/filter.json b/pype/tools/config_setting/config/project_presets/plugins/global/filter.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/filter.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/load.json b/pype/tools/config_setting/config/project_presets/plugins/global/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/publish.json b/pype/tools/config_setting/config/project_presets/plugins/global/publish.json new file mode 100644 index 0000000000..6e51e00497 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/publish.json @@ -0,0 +1,73 @@ +{ + "IntegrateMasterVersion": { + "enabled": false + }, + "ExtractReview": { + "__documentation__": "http://pype.club/docs/admin_presets_plugins", + "profiles": [ + { + "families": [], + "hosts": [], + "outputs": { + "h264": { + "filter": { + "families": ["render", "review", "ftrack"] + }, + "ext": "mp4", + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "video_filters": [], + "audio_filters": [], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + }, + "tags": ["burnin", "ftrackreview"] + } + } + } + ] + }, + "ExtractBurnin": { + "options": { + "opacity": 1, + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5, + "bg_opacity": 0.5, + "font_size": 42 + }, + "fields": { + + }, + "profiles": [ + { + "burnins": { + "burnin": { + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_RIGHT": "{anatomy[version]}", + "TOP_CENTERED": "", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "BOTTOM_CENTERED": "{asset}", + "BOTTOM_LEFT": "{username}" + } + } + } + ] + }, + "IntegrateAssetNew": { + "template_name_profiles": { + "publish": { + "families": [], + "tasks": [] + }, + "render": { + "families": ["review", "render", "prerender"] + } + } + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/create.json b/pype/tools/config_setting/config/project_presets/plugins/maya/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json b/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json new file mode 100644 index 0000000000..83d6f05f31 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json @@ -0,0 +1,9 @@ +{ + "Preset n1": { + "ValidateNoAnimation": false, + "ValidateShapeDefaultNames": false + }, + "Preset n2": { + "ValidateNoAnimation": false + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/load.json b/pype/tools/config_setting/config/project_presets/plugins/maya/load.json new file mode 100644 index 0000000000..260fbb35ee --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/load.json @@ -0,0 +1,18 @@ +{ + "colors": { + "model": [0.821, 0.518, 0.117], + "rig": [0.144, 0.443, 0.463], + "pointcache": [0.368, 0.821, 0.117], + "animation": [0.368, 0.821, 0.117], + "ass": [1.0, 0.332, 0.312], + "camera": [0.447, 0.312, 1.0], + "fbx": [1.0, 0.931, 0.312], + "mayaAscii": [0.312, 1.0, 0.747], + "setdress": [0.312, 1.0, 0.747], + "layout": [0.312, 1.0, 0.747], + "vdbcache": [0.312, 1.0, 0.428], + "vrayproxy": [0.258, 0.95, 0.541], + "yeticache": [0.2, 0.8, 0.3], + "yetiRig": [0, 0.8, 0.5] + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json b/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json new file mode 100644 index 0000000000..2e2b3164f3 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json @@ -0,0 +1,17 @@ +{ + "ValidateModelName": { + "enabled": false, + "material_file": "/path/to/shader_name_definition.txt", + "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" + }, + "ValidateAssemblyName": { + "enabled": false + }, + "ValidateShaderName": { + "enabled": false, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": false + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json b/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json new file mode 100644 index 0000000000..2872b783cb --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json @@ -0,0 +1,54 @@ +[{ + "tasks": ["lighting"], + + "current_context": [{ + "subset_name_filters": [".+[Mm]ain"], + "families": ["model"], + "repre_names": ["abc", "ma"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["animation", "pointcache"], + "repre_names": ["abc"], + "loaders": ["ReferenceLoader"] + },{ + "families": ["rendersetup"], + "repre_names": ["json"], + "loaders": ["RenderSetupLoader"] + }, { + "families": ["camera"], + "repre_names": ["abc"], + "loaders": ["ReferenceLoader"] + }], + + "linked_assets": [{ + "families": ["setdress"], + "repre_names": ["ma"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["ass"], + "repre_names": ["ass"], + "loaders":["assLoader"] + }] +}, { + "tasks": ["animation"], + + "current_context": [{ + "families": ["camera"], + "repre_names": ["abc", "ma"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["audio"], + "repre_names": ["wav"], + "loaders": ["RenderSetupLoader"] + }], + + "linked_assets": [{ + "families": ["setdress"], + "repre_names": ["proxy"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["rig"], + "repre_names": ["ass"], + "loaders": ["rigLoader"] + }] +}] diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json new file mode 100644 index 0000000000..4deb0b4ad5 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json @@ -0,0 +1,8 @@ +{ + "CreateWriteRender": { + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + }, + "CreateWritePrerender": { + "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json new file mode 100644 index 0000000000..ab0d0e76a5 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json @@ -0,0 +1,48 @@ +{ + "ExtractThumbnail": { + "nodes": { + "Reformat": [ + ["type", "to format"], + ["format", "HD_1080"], + ["filter", "Lanczos6"], + ["black_outside", true], + ["pbb", false] + ] + } + }, + "ValidateNukeWriteKnobs": { + "enabled": false, + "knobs": { + "render": { + "review": true + } + } + }, + "ExtractReviewDataLut": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", + "enabled": "keep in on `false` if you want Nuke baking colorspace workflow applied else FFmpeg will convert imgsequence with baked LUT" + }, + "enabled": false + }, + "ExtractReviewDataMov": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", + "enabled": "keep in on `true` if you want Nuke baking colorspace workflow applied" + }, + "enabled": true, + "viewer_lut_raw": false + }, + "ExtractSlateFrame": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`" + }, + "viewer_lut_raw": false + }, + "NukeSubmitDeadline": { + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_chunk_size": 1 + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json new file mode 100644 index 0000000000..d3613c929e --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json @@ -0,0 +1,11 @@ +[{ + "tasks": ["compositing"], + + "current_context": [{ + "families": ["render", "plate"], + "repre_names": ["exr" ,"dpx"], + "loaders": ["LoadSequence"] + }], + + "linked_assets": [] +}] diff --git a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json new file mode 100644 index 0000000000..bd6a0dc1bd --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json @@ -0,0 +1,10 @@ +{ + "strict": { + "ValidateVersion": true, + "VersionUpWorkfile": true + }, + "benevolent": { + "ValidateVersion": false, + "VersionUpWorkfile": false + } +} \ No newline at end of file diff --git a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json new file mode 100644 index 0000000000..8c4ad133f1 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json @@ -0,0 +1,8 @@ +{ + "CollectInstanceVersion": { + "enabled": false + }, + "ExtractReviewCutUpVideo": { + "tags_addition": [] + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json b/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json new file mode 100644 index 0000000000..ecfff12db9 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json @@ -0,0 +1,17 @@ +{ + "ExtractReviewSP": { + "outputs": { + "h264": { + "input": [ + "-gamma 2.2" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18" + ], + "tags": ["preview"], + "ext": "mov" + } + } + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/test/create.json b/pype/tools/config_setting/config/project_presets/plugins/test/create.json new file mode 100644 index 0000000000..fa0b2fc05f --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/test/create.json @@ -0,0 +1,8 @@ +{ + "MyTestCreator": { + "my_test_property": "B", + "active": false, + "new_property": "new", + "family": "new_family" + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/test/publish.json b/pype/tools/config_setting/config/project_presets/plugins/test/publish.json new file mode 100644 index 0000000000..3180dd5d8a --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/test/publish.json @@ -0,0 +1,10 @@ +{ + "MyTestPlugin": { + "label": "loaded from preset", + "optional": true, + "families": ["changed", "by", "preset"] + }, + "MyTestRemovedPlugin": { + "enabled": false + } +} diff --git a/pype/tools/config_setting/config/project_presets/premiere/asset_default.json b/pype/tools/config_setting/config/project_presets/premiere/asset_default.json new file mode 100644 index 0000000000..84d2bde3d8 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/premiere/asset_default.json @@ -0,0 +1,5 @@ +{ + "frameStart": 1001, + "handleStart": 0, + "handleEnd": 0 +} diff --git a/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json b/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json new file mode 100644 index 0000000000..333c9cd70b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json @@ -0,0 +1,21 @@ +{ + "defaultTasks": ["Layout", "Animation"], + "taskToSubsets": { + "Layout": ["reference", "audio"], + "Animation": ["audio"] + }, + "subsetToRepresentations": { + "reference": { + "preset": "h264", + "representation": "mp4" + }, + "thumbnail": { + "preset": "jpeg_thumb", + "representation": "jpg" + }, + "audio": { + "preset": "48khz", + "representation": "wav" + } + } +} diff --git a/pype/tools/config_setting/config/project_presets/unreal/project_setup.json b/pype/tools/config_setting/config/project_presets/unreal/project_setup.json new file mode 100644 index 0000000000..8a4dffc526 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/unreal/project_setup.json @@ -0,0 +1,4 @@ +{ + "dev_mode": false, + "install_unreal_python_engine": false +} diff --git a/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json b/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json b/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json new file mode 100644 index 0000000000..1ba8e9b511 --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json @@ -0,0 +1,5 @@ +{ + "TestAction": { + "ignore_me": true + } +} diff --git a/pype/tools/config_setting/config/studio_presets/global/applications.json b/pype/tools/config_setting/config/studio_presets/global/applications.json new file mode 100644 index 0000000000..35e399444c --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/applications.json @@ -0,0 +1,39 @@ +{ + "blender_2.80": true, + "blender_2.81": true, + "blender_2.82": true, + "blender_2.83": true, + "harmony_17": true, + "houdini_16": true, + "houdini_17": true, + "houdini_18": true, + "maya_2016": true, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nuke_10.0": true, + "nuke_11.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": true, + "nukex_10.0": true, + "nukex_11.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nukestudio_10.0": true, + "nukestudio_11.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "photoshop_2020": true, + "premiere_2019": true, + "premiere_2020": true, + "python_2": true, + "python_3": true, + "resolve_16": true, + "shell": true, + "storyboardpro_7": true, + "unreal_4.21": true +} diff --git a/pype/tools/config_setting/config/studio_presets/global/es/applications.json b/pype/tools/config_setting/config/studio_presets/global/es/applications.json new file mode 100644 index 0000000000..35e399444c --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/es/applications.json @@ -0,0 +1,39 @@ +{ + "blender_2.80": true, + "blender_2.81": true, + "blender_2.82": true, + "blender_2.83": true, + "harmony_17": true, + "houdini_16": true, + "houdini_17": true, + "houdini_18": true, + "maya_2016": true, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nuke_10.0": true, + "nuke_11.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": true, + "nukex_10.0": true, + "nukex_11.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nukestudio_10.0": true, + "nukestudio_11.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "photoshop_2020": true, + "premiere_2019": true, + "premiere_2020": true, + "python_2": true, + "python_3": true, + "resolve_16": true, + "shell": true, + "storyboardpro_7": true, + "unreal_4.21": true +} diff --git a/pype/tools/config_setting/config/studio_presets/global/intents.json b/pype/tools/config_setting/config/studio_presets/global/intents.json new file mode 100644 index 0000000000..c853e27b7f --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/intents.json @@ -0,0 +1,9 @@ +{ + "default": "wip", + "items": { + "": "", + "wip": "WIP", + "test": "TEST", + "final": "FINAL" + } +} diff --git a/pype/tools/config_setting/config/studio_presets/global/tray_items.json b/pype/tools/config_setting/config/studio_presets/global/tray_items.json new file mode 100644 index 0000000000..a42bf67c38 --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/tray_items.json @@ -0,0 +1,25 @@ +{ + "usage": { + "User settings": false, + "Ftrack": false, + "Muster": false, + "Avalon": true, + "Clockify": false, + "Standalone Publish": true, + "Logging": true, + "Idle Manager": true, + "Timers Manager": true, + "Rest Api": true, + "Premiere Communicator": true + }, + "attributes": { + "Rest Api": { + "default_port": 8021, + "exclude_ports": [] + }, + "Timers Manager": { + "full_time": 15, + "message_time": 0.5 + } + } +} diff --git a/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json b/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json new file mode 100644 index 0000000000..4edab9077d --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json @@ -0,0 +1,19 @@ +{ + "3delight": 41, + "arnold": 46, + "arnold_sf": 57, + "gelato": 30, + "harware": 3, + "krakatoa": 51, + "file_layers": 7, + "mentalray": 2, + "mentalray_sf": 6, + "redshift": 55, + "renderman": 29, + "software": 1, + "software_sf": 5, + "turtle": 10, + "vector": 4, + "vray": 37, + "ffmpeg": 48 +} diff --git a/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json b/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json new file mode 100644 index 0000000000..096964b5b8 --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json @@ -0,0 +1,153 @@ +{ + "key": "applications", + "type": "dict-expanding", + "label": "Applications", + "children": [ + { + "type": "dict-form", + "children": [ + { + "key": "blender_2.80", + "type": "boolean", + "label": "Blender 2.80" + }, { + "key": "blender_2.81", + "type": "boolean", + "label": "Blender 2.81" + }, { + "key": "blender_2.82", + "type": "boolean", + "label": "Blender 2.82" + }, { + "key": "blender_2.83", + "type": "boolean", + "label": "Blender 2.83" + }, { + "key": "celaction_local", + "type": "boolean", + "label": "Celaction Local" + }, { + "key": "celaction_remote", + "type": "boolean", + "label": "Celaction Remote" + }, { + "key": "harmony_17", + "type": "boolean", + "label": "Harmony 17" + }, { + "key": "houdini_16", + "type": "boolean", + "label": "Houdini 16" + }, { + "key": "houdini_17", + "type": "boolean", + "label": "Houdini 17" + }, { + "key": "houdini_18", + "type": "boolean", + "label": "Houdini 18" + }, { + "key": "maya_2017", + "type": "boolean", + "label": "Autodest Maya 2017" + }, { + "key": "maya_2018", + "type": "boolean", + "label": "Autodest Maya 2018" + }, { + "key": "maya_2019", + "type": "boolean", + "label": "Autodest Maya 2019" + }, { + "key": "maya_2020", + "type": "boolean", + "label": "Autodest Maya 2020" + }, { + "key": "nuke_10.0", + "type": "boolean", + "label": "Nuke 10.0" + }, { + "key": "nuke_11.2", + "type": "boolean", + "label": "Nuke 11.2" + }, { + "key": "nuke_11.3", + "type": "boolean", + "label": "Nuke 11.3" + }, { + "key": "nuke_12.0", + "type": "boolean", + "label": "Nuke 12.0" + }, { + "key": "nukex_10.0", + "type": "boolean", + "label": "NukeX 10.0" + }, { + "key": "nukex_11.2", + "type": "boolean", + "label": "NukeX 11.2" + }, { + "key": "nukex_11.3", + "type": "boolean", + "label": "NukeX 11.3" + }, { + "key": "nukex_12.0", + "type": "boolean", + "label": "NukeX 12.0" + }, { + "key": "nukestudio_10.0", + "type": "boolean", + "label": "NukeStudio 10.0" + }, { + "key": "nukestudio_11.2", + "type": "boolean", + "label": "NukeStudio 11.2" + }, { + "key": "nukestudio_11.3", + "type": "boolean", + "label": "NukeStudio 11.3" + }, { + "key": "nukestudio_12.0", + "type": "boolean", + "label": "NukeStudio 12.0" + }, { + "key": "houdini_16.5", + "type": "boolean", + "label": "Houdini 16.5" + }, { + "key": "houdini_17", + "type": "boolean", + "label": "Houdini 17" + }, { + "key": "houdini_18", + "type": "boolean", + "label": "Houdini 18" + }, { + "key": "premiere_2019", + "type": "boolean", + "label": "Premiere 2019" + }, { + "key": "premiere_2020", + "type": "boolean", + "label": "Premiere 2020" + }, { + "key": "premiere_2020", + "type": "boolean", + "label": "Premiere 2020" + }, { + "key": "resolve_16", + "type": "boolean", + "label": "BM DaVinci Resolve 16" + }, { + "key": "storyboardpro_7", + "type": "boolean", + "label": "Storyboard Pro 7" + }, { + "key": "unreal_4.24", + "type": "boolean", + "label": "Unreal Editor 4.24" + } + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json new file mode 100644 index 0000000000..febf84eb4a --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json @@ -0,0 +1,30 @@ +{ + "key": "ftrack", + "type": "dict-expanding", + "label": "Ftrack", + "children": [ + { + "key": "status_update", + "type": "dict-expanding", + "label": "Status updates", + "children": [ + { + "key": "Ready", + "type": "text-singleline", + "label": "Ready" + } + ] + }, { + "key": "status_version_to_task", + "type": "dict-expanding", + "label": "Version status to Task status", + "children": [ + { + "key": "in progress", + "type": "text-singleline", + "label": "In Progress" + } + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json new file mode 100644 index 0000000000..38c07ec33d --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json @@ -0,0 +1,13 @@ +{ + "key": "studio", + "type": "dict-invisible", + "label": "Studio", + "children": [ + { + "type": "schema", + "children": [ + "ftrack_projects_gui_schema" + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json new file mode 100644 index 0000000000..7d902bb8db --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json @@ -0,0 +1,23 @@ +{ + "key": "studio", + "type": "dict-invisible", + "label": "Studio", + "children": [ + { + "type": "schema", + "children": [ + "applications_gui_schema", + "tools_gui_schema" + ] + }, { + "key": "muster", + "type": "dict-invisible", + "children": [{ + "key": "templates_mapping", + "label": "Muster", + "type": "dict-modifiable", + "object_type": "int" + }] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json b/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json new file mode 100644 index 0000000000..2f46ef0ec5 --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json @@ -0,0 +1,29 @@ +{ + "key": "tools", + "type": "dict-expanding", + "label": "Tools", + "children": [ + { + "type": "dict-form", + "children": [ + { + "key": "mtoa_3.0.1", + "type": "boolean", + "label": "Arnold Maya 3.0.1" + }, { + "key": "mtoa_3.1.1", + "type": "boolean", + "label": "Arnold Maya 3.1.1" + }, { + "key": "mtoa_3.2.0", + "type": "boolean", + "label": "Arnold Maya 3.2.0" + }, { + "key": "yeti_2.1.2", + "type": "boolean", + "label": "Yeti 2.1.2" + } + ] + } + ] +} diff --git a/pype/tools/config_setting/interface.py b/pype/tools/config_setting/interface.py new file mode 100644 index 0000000000..e95f3f5fe3 --- /dev/null +++ b/pype/tools/config_setting/interface.py @@ -0,0 +1,49 @@ +import os +import sys +os.environ["PYPE_CONFIG"] = ( + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype-config" +) +os.environ["AVALON_MONGO"] = "mongodb://localhost:2707" +sys_paths = ( + "C:/Users/Public/pype_env2/Lib/site-packages", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/avalon-core", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pyblish-base", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pyblish-lite", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype-config" +) +for path in sys_paths: + sys.path.append(path) + +from widgets import main +import style +from Qt import QtWidgets, QtGui + + +class MyApp(QtWidgets.QApplication): + def __init__(self, *args, **kwargs): + super(MyApp, self).__init__(*args, **kwargs) + stylesheet = style.load_stylesheet() + self.setStyleSheet(stylesheet) + self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + + +if __name__ == "__main__": + app = MyApp(sys.argv) + + # main_widget = QtWidgets.QWidget() + # main_widget.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + # + # layout = QtWidgets.QVBoxLayout(main_widget) + # + # widget = main.MainWidget(main_widget) + + # layout.addWidget(widget) + # main_widget.setLayout(layout) + # main_widget.show() + + widget = main.MainWidget() + widget.show() + + sys.exit(app.exec_()) diff --git a/pype/tools/config_setting/style/__init__.py b/pype/tools/config_setting/style/__init__.py new file mode 100644 index 0000000000..a8f202d97b --- /dev/null +++ b/pype/tools/config_setting/style/__init__.py @@ -0,0 +1,12 @@ +import os + + +def load_stylesheet(): + style_path = os.path.join(os.path.dirname(__file__), "style.css") + with open(style_path, "r") as style_file: + stylesheet = style_file.read() + return stylesheet + + +def app_icon_path(): + return os.path.join(os.path.dirname(__file__), "pype_icon.png") diff --git a/pype/tools/config_setting/style/pype_icon.png b/pype/tools/config_setting/style/pype_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bfacf6eeedc753db0d77e661843a78b0eb13ce38 GIT binary patch literal 3793 zcmai1XH=6*x6Ye{CK8MY2ok{)4xlKAASI|oK|&G1NG}mf1QaAla)1M9Vx@*AARG=| zP(e_zUxodrEz3*Bxd-m+vduBh+o{4ukPEgpO zz5xbD!#x!nHCEY!;)_rS{8J6d7GObJpmL-tP0Zl4m(l^NyV<{AlB#8n@>Oo3}NptV#uE zJ|o&Vn3{fQzV*eT>weC;f2IfKUk*oieHjXWt$u9R&iap152)_*D{Q9Z=F!{JIe)SO z%hR$_iz|#7@mk8$pHYcwq2D{bGrQIE=_ z%HJG!GSG{;`1R9ojdwK+=dxS9yRD=@oIg!qCpD2PwV&R3%+AYq-B-Rk#EpqRn42$e zH>EOb!%a~%M3rp~*r<3t>D^S3rWvB9_(;Q~?$xZM(Ovs0MtPn)j$hk-{6skM;4UMb zKV5Cp+5JBoWB+s3dgcu6mFk83jh`Q>2K%`5nIv^tH|c2{{two-)KQXy*m^}ZwzhO} z%KAMn&oAq#2$dI#=U!LuIbuc3tlO`0ov#$=N&j&1zR#|usfdEl(Xv&o9)7Q9wlB0w z%Em%{ivvE5@OL~0hayL@^9qN-46y4z6nW3;4;XCYqP@*w*T}r-!K)%N&5!#{x9*){ zhzXWASaS!qGo?GY?l$su#|0v%Q0;A5E;A>?*mVC%wJDSL=4$tivf}O;AJ@fXsLA== zEOKfsx|h|o`;*Y?jJ=BV+1DNTEg$G>Jk1H*o>$A!jmun%;blqQN=!%I)jzUkms>fp zv0Vq3qr0T+)3yK0Cug|}C!j?6ZI{>BHdqu8$!bfOwI$r&7OTOiU<}94bfcG`6vVJv zxW95=t({(PROyoy zmEu2x|DjmG9bO;u*B6;_lEHeI0b@M^LB{T+rwkh{HhHXD-M&0Q%aJluxD^0*=x^lZ zVPe>A3EYyi#IYPY@i|(&iE@>cojpdA3M=0G+f)}(&^0+{ILA7mgDX?ifc8`jm%MZrr3+I7ne?oFo!pC3p>e4AQQ5%$dRX0Q zDvz@Blmiu6I)2b38^H%lO$qNl8<(~FIZO1vNXqU?M0q(f=?llI@4fV|+krdx$Q<62 zwoi~4<(Iz|Q=_5*1+h;uw#~B+?7)>?WYVA3IC0A7SQ-m{FELP1jafHaF6c2(qJ_3j z&N-K0StGJ`?9SV4d}r^ z4_ldzCUok@j0sxQ32JjCkonCfpzda3wVq8Tu?^;habD(d;>s0$oWBZ_-rL|f&suE| z#sygbVLpOwfji?3#K|vgWsbF@Q(0l|r0f72!ZBczHJ>G<2Z6~E263pnx3F5YiF;rV zH)Ect6YyU@7~05Gz6DxivUZL$t89amWaiyw(5JyEm#EF!z-WCz3hM4TKPr8ENm;lJZOUK(TQ+b6EU1GWUTb_^}rU5-W?M%i0#!NwN<)U4-ZBIhkHHZDu zyl@@67el~bUu@(-@uDwu>B&+d-6m3}@bzV6S*;lGtAU`i zJ$cHm=J3u&RwC<;#gRU6lZnG?|zw#X3(val5R>Nk@EOgPg9i1=CLe{XNtf z1j&QkVpeSZ5s+-fin_=c1Rsz3at*8Hw6v*HjRO$q2X-@a&8%}j{V;n=-DUz_efD%N zRA>2%VX39loVUG~luQA8{aL1nnDFrWMxnm*_EN=YEjDLxEFV^i13^C=sojV0C6bTM zS{<&FKZNP7S<%g%_~~c?6Xejd_B`C?uoCzaF;({R_HYr`qoOi$@4jHbxL;WEVqa?WX%sA)p^LJ7yVkG78CtK> zrC*~p7}VI8Z3&$`v_nCok=`B)0^a*FILT3PIIZ9m)5DRazfS;K{Rf?zl?O_~|F7T^ z|BMO{wjt7jvi&jSdG#G6>fXWK?!S$)>=H^Imkr!GN|Wr?k{Lq!BJ=irs&2g8Q84%y ze@sPC%L%b&sU=_w4{uxR{rS7VLC`DJ(3=8X-HU6;<&T{geBY6WiFCqW&*YC;a2k6C zaw5|p^MRcl8fS@Z&=vA+^42o4vyS0G38778fA+jBw z&f&JAlMDspaO52}+u)SVyxK-xn-?%H!pGLxF|A8c&8G~mSXQ`oxS#3b^9SyudzAYp+{&24XX|s-CR!gj&4W0 zJiL-Nu7Hn?Kv53=DqQKt-&?=u3z=%{@~!{`lTM`Sh!TWxYI&&OB&H`wlP!K*Fnezg znOedfcz+cm#=HMLPr%!Q>%u*b8V?g5d6blGn8;iw?h7Og<}p3I81Xg?c_&wUxXQAp z{SN~E!Iv>akVAcN1Q3!ArIz*npD=A1=b45r>@AI{n2r5K_zs=cJV;(heg&ilG=MqM zbFi?J;nm#Z53;9$d-MKO4t(iX<G)L3gY|#He!w|;Fp?XY~aoINns|b10Kzw&Kh|%eN&b ziL-@qNbT4S>P2k{D-1=L=DW!`nZQGQ%GwTYVo+7abkR#q!7>xD$ik;F!hd;6fVT#2 b;3u>qRjE!lxug$XMKJrL$8D;P_+R@UX<7IA literal 0 HcmV?d00001 diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css new file mode 100644 index 0000000000..f559a4c3b3 --- /dev/null +++ b/pype/tools/config_setting/style/style.css @@ -0,0 +1,90 @@ +QWidget { + color: #bfccd6; + background-color: #293742; + font-size: 12px; +} + +QCheckBox::indicator { +} +QCheckBox::indicator:focus { + color: #ff0000; +} + +QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit { + border: 1px solid #aaaaaa; + border-radius: 3px; +} + +QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus { + border: 1px solid #ffffff; +} + +QLabel[state="original"] {} + +QLabel[state="modified"] { + color: #137cbd; +} + +QLabel[state="overriden-modified"] { + color: #00b386; +} + +QLabel[state="overriden"] { + color: #ff8c1a; +} + +QWidget[input-state="original"] {} +QWidget[input-state="modified"] { + border-color: #137cbd; +} + +QWidget[input-state="overriden-modified"] { + border-color: #00b386; +} + +QWidget[input-state="overriden"] { + border-color: #ff8c1a; +} + +QPushButton[btn-type="text-list"] { + border: 1px solid #bfccd6; + border-radius: 10px; +} + +QPushButton[btn-type="text-list"]:hover { + border-color: #137cbd; + color: #137cbd; +} + +QPushButton[btn-type="expand-toggle"] { + background: transparent; +} + +#DictKey[state="original"] {} + +#DictKey[state="modified"] { + border-color: #137cbd; +} + +#DictKey[state="overriden"] { + border-color: #00f; +} +#DictKey[state="overriden-modified"] { + border-color: #0f0; +} + +#ExpandLabel { + background: transparent; +} + +#DictExpandWidget, #ModifiableDict, #ExpandingWidget { + border: 1px solid #455c6e; + border-radius: 3px; + background: #1d272f; +} + +#TextListSubWidget { + border: 1px solid #455c6e; + border-radius: 3px; + background: #1d272f; +} diff --git a/pype/tools/config_setting/widgets/__init__.py b/pype/tools/config_setting/widgets/__init__.py new file mode 100644 index 0000000000..b295759a36 --- /dev/null +++ b/pype/tools/config_setting/widgets/__init__.py @@ -0,0 +1,6 @@ +from .lib import CustomNone, NOT_SET + + +from .base import * +from .main import * +from .inputs import * diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py new file mode 100644 index 0000000000..0cc64a66de --- /dev/null +++ b/pype/tools/config_setting/widgets/base.py @@ -0,0 +1,282 @@ +import os +import json +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .lib import NOT_SET +from avalon import io + + +class TypeToKlass: + types = {} + + +class ClickableWidget(QtWidgets.QLabel): + clicked = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(ClickableWidget, self).__init__(*args, **kwargs) + self.setObjectName("ExpandLabel") + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit() + super(ClickableWidget, self).mouseReleaseEvent(event) + + +class PypeConfigurationWidget: + is_category = False + is_overriden = False + is_modified = False + + def config_value(self): + raise NotImplementedError( + "Method `config_value` is not implemented for `{}`.".format( + self.__class__.__name__ + ) + ) + + def value_from_values(self, values, keys=None): + if not values: + return NOT_SET + + if keys is None: + keys = self.keys + + value = values + for key in keys: + if not isinstance(value, dict): + raise TypeError( + "Expected dictionary got {}.".format(str(type(value))) + ) + + if key not in value: + return NOT_SET + value = value[key] + return value + + def add_children_gui(self, child_configuration, values): + raise NotImplementedError(( + "Method `add_children_gui` is not implemented for `{}`." + ).format(self.__class__.__name__)) + + +class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): + config_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema" + ) + is_overidable = False + + def __init__(self, parent=None): + super(StudioWidget, self).__init__(parent) + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + # scroll_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + # scroll_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + values = {"studio": config.studio_presets()} + schema = config.gui_schema("studio_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + btn = QtWidgets.QPushButton("Finish") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(btn, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(scroll_widget, 1) + layout.addWidget(footer_widget, 0) + + btn.clicked.connect(self.___finish) + + def ___finish(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + print(json.dumps(output, indent=4)) + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + +class ProjectListWidget(QtWidgets.QWidget): + default = "< Default >" + + def __init__(self, parent=None): + super(ProjectListWidget, self).__init__(parent) + + label = QtWidgets.QLabel("Project") + project_list = QtWidgets.QListView(self) + project_list.setModel(QtGui.QStandardItemModel()) + + layout = QtWidgets.QVBoxLayout(self) + # content_margin = 5 + # layout.setContentsMargins( + # content_margin, + # content_margin, + # content_margin, + # content_margin + # ) + # layout.setSpacing(3) + layout.addWidget(label, 0) + layout.addWidget(project_list, 1) + + self.project_list = project_list + + self.refresh() + + def project_name(self): + current_selection = self.project_list.currentText() + if current_selection == self.default: + return None + return current_selection + + def refresh(self): + selected_project = None + for index in self.project_list.selectedIndexes(): + selected_project = index.data(QtCore.Qt.DisplayRole) + break + + model = self.project_list.model() + model.clear() + items = [self.default] + io.install() + for project_doc in tuple(io.projects()): + print(project_doc["name"]) + items.append(project_doc["name"]) + + for item in items: + model.appendRow(QtGui.QStandardItem(item)) + + if not selected_project: + selected_project = self.default + + found_items = model.findItems(selected_project) + if found_items: + index = model.indexFromItem(found_items[0]) + c = QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent + self.project_list.selectionModel().select( + index, c + ) + # self.project_list.selectionModel().setCurrentIndex( + # index, c + # ) + + + +class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): + config_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema" + ) + is_overidable = True + + def __init__(self, parent=None): + super(ProjectWidget, self).__init__(parent) + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + project_list_widget = ProjectListWidget() + content_layout.addWidget(project_list_widget) + + self.project_list_widget = project_list_widget + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + values = config.project_presets() + schema = config.gui_schema("project_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + btn = QtWidgets.QPushButton("Finish") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(btn, 0) + + presets_widget = QtWidgets.QWidget() + presets_layout = QtWidgets.QVBoxLayout(presets_widget) + presets_layout.setContentsMargins(0, 0, 0, 0) + presets_layout.setSpacing(0) + + presets_layout.addWidget(scroll_widget, 1) + presets_layout.addWidget(footer_widget, 0) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(project_list_widget, 0) + layout.addWidget(presets_widget, 1) + + btn.clicked.connect(self.___finish) + + def ___finish(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + print(json.dumps(output, indent=4)) + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py new file mode 100644 index 0000000000..335299cb2f --- /dev/null +++ b/pype/tools/config_setting/widgets/config.py @@ -0,0 +1,236 @@ +import os +import json +import logging +import copy + +# DEBUG SETUP +os.environ["AVALON_PROJECT"] = "kuba_each_case" +os.environ["PYPE_PROJECT_CONFIGS"] = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config", + "project_overrides" +) +# + +log = logging.getLogger(__name__) + +config_path = os.path.dirname(os.path.dirname(__file__)) +studio_presets_path = os.path.normpath( + os.path.join(config_path, "config", "studio_presets") +) +project_presets_path = os.path.normpath( + os.path.join(config_path, "config", "project_presets") +) +first_run = False + +OVERRIDE_KEY = "__overriden__" +POP_KEY = "__popkey__" + + +def load_json(fpath): + # Load json data + with open(fpath, "r") as opened_file: + lines = opened_file.read().splitlines() + + # prepare json string + standard_json = "" + for line in lines: + # Remove all whitespace on both sides + line = line.strip() + + # Skip blank lines + if len(line) == 0: + continue + + standard_json += line + + # Check if has extra commas + extra_comma = False + if ",]" in standard_json or ",}" in standard_json: + extra_comma = True + standard_json = standard_json.replace(",]", "]") + standard_json = standard_json.replace(",}", "}") + + global first_run + if extra_comma and first_run: + log.error("Extra comma in json file: \"{}\"".format(fpath)) + + # return empty dict if file is empty + if standard_json == "": + if first_run: + log.error("Empty json file: \"{}\"".format(fpath)) + return {} + + # Try to parse string + try: + return json.loads(standard_json) + + except json.decoder.JSONDecodeError: + # Return empty dict if it is first time that decode error happened + if not first_run: + return {} + + # Repreduce the exact same exception but traceback contains better + # information about position of error in the loaded json + try: + with open(fpath, "r") as opened_file: + json.load(opened_file) + + except json.decoder.JSONDecodeError: + log.warning( + "File has invalid json format \"{}\"".format(fpath), + exc_info=True + ) + + return {} + + +def subkey_merge(_dict, value, keys, with_metadata=False): + key = keys.pop(0) + if not keys: + if with_metadata: + _dict[key] = {"type": "file", "value": value} + else: + _dict[key] = value + return _dict + + if key not in _dict: + if with_metadata: + _dict[key] = {"type": "folder", "value": {}} + else: + _dict[key] = {} + + if with_metadata: + sub_dict = _dict[key]["value"] + else: + sub_dict = _dict[key] + + _value = subkey_merge(sub_dict, value, keys, with_metadata) + if with_metadata: + _dict[key]["value"] = _value + else: + _dict[key] = _value + return _dict + + +def load_jsons_from_dir(path, *args, **kwargs): + output = {} + + path = os.path.normpath(path) + if not os.path.exists(path): + # TODO warning + return output + + with_metadata = kwargs.get("with_metadata") + sub_keys = list(kwargs.pop("subkeys", args)) + for sub_key in tuple(sub_keys): + _path = os.path.join(path, sub_key) + if not os.path.exists(_path): + break + + path = _path + sub_keys.pop(0) + + base_len = len(path) + 1 + ext_len = len(".json") + + for base, _directories, filenames in os.walk(path): + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext == ".json": + full_path = os.path.join(base, filename) + value = load_json(full_path) + + # dict_path = os.path.join(base[base_len:], basename) + # dict_keys = dict_path.split(os.path.sep) + dict_keys = base[base_len:].split(os.path.sep) + [basename] + output = subkey_merge(output, value, dict_keys, with_metadata) + + for sub_key in sub_keys: + output = output[sub_key] + return output + + +def studio_presets(*args, **kwargs): + return load_jsons_from_dir(studio_presets_path, *args, **kwargs) + + +def global_project_presets(**kwargs): + return load_jsons_from_dir(project_presets_path, **kwargs) + + +def studio_presets_with_metadata(*args, **kwargs): + return load_jsons_from_dir(studio_presets_path, *args, **kwargs) + + +def global_project_presets_with_metadata(**kwargs): + return load_jsons_from_dir(project_presets_path, **kwargs) + + +def project_preset_overrides(project_name, **kwargs): + project_configs_path = os.environ.get("PYPE_PROJECT_CONFIGS") + if project_name and project_configs_path: + return load_jsons_from_dir( + os.path.join(project_configs_path, project_name), + **kwargs + ) + return {} + + +def merge_overrides(global_dict, override_dict): + if OVERRIDE_KEY in override_dict: + _override = override_dict.pop(OVERRIDE_KEY) + if _override: + return override_dict + + for key, value in override_dict.items(): + if value == POP_KEY: + global_dict.pop(key) + + elif key == OVERRIDE_KEY: + continue + + elif key not in global_dict: + global_dict[key] = value + + elif isinstance(value, dict) and isinstance(global_dict[key], dict): + global_dict[key] = merge_overrides(global_dict[key], value) + + else: + global_dict[key] = value + return global_dict + + +def apply_overrides(global_presets, project_overrides): + global_presets = copy.deepcopy(global_presets) + if not project_overrides: + return global_presets + return merge_overrides(global_presets, project_overrides) + + +def project_presets(project_name=None, **kwargs): + global_presets = global_project_presets(**kwargs) + + if not project_name: + project_name = os.environ.get("AVALON_PROJECT") + project_overrides = project_preset_overrides(project_name, **kwargs) + + return apply_overrides(global_presets, project_overrides) + + +def gui_schema(schema_name): + filename = schema_name + ".json" + schema_folder = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema", + filename + ) + with open(schema_folder, "r") as json_stream: + schema = json.load(json_stream) + return schema + + +p1 = studio_presets(with_metadata=True) +p2 = studio_presets(with_metadata=False) +print(json.dumps(p1, indent=4)) +print(json.dumps(p2, indent=4)) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py new file mode 100644 index 0000000000..1ddc27278d --- /dev/null +++ b/pype/tools/config_setting/widgets/inputs.py @@ -0,0 +1,1346 @@ +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .base import PypeConfigurationWidget, TypeToKlass, ClickableWidget +from .lib import NOT_SET, AS_WIDGET + +import json + + +class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._as_widget = values is AS_WIDGET + self._parent = parent + + super(BooleanWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.checkbox = QtWidgets.QCheckBox() + self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(label_widget) + + layout.addWidget(self.checkbox) + + if not self._as_widget: + self.label_widget = label_widget + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.checkbox.setChecked(value) + + self.origin_value = self.item_value() + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_value(self, value, origin_value=False): + self.checkbox.setChecked(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.reset_value() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + if not self._as_widget: + property_name = "input-state" + else: + property_name = "state" + + self.label_widget.setProperty(property_name, state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.checkbox.isChecked() + + def config_value(self): + return {self.key: self.item_value()} + + +class ModifiedIntSpinBox(QtWidgets.QSpinBox): + def __init__(self, *args, **kwargs): + super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + super(ModifiedIntSpinBox, self).wheelEvent(event) + else: + event.ignore() + + +class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): + def __init__(self, *args, **kwargs): + super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + super(ModifiedFloatSpinBox, self).wheelEvent(event) + else: + event.ignore() + + +class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + super(IntegerWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.int_input = ModifiedIntSpinBox() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.int_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.int_input.setValue(value) + + self.origin_value = self.item_value() + + self.int_input.valueChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.int_input.setValue(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def clear_value(self): + self.set_value(0) + + def reset_value(self): + self.set_value(self.origin_value) + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + if self._as_widget: + property_name = "input-state" + widget = self.int_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.int_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + super(FloatWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.float_input = ModifiedFloatSpinBox() + + decimals = input_data.get("decimals", 5) + maximum = input_data.get("maximum") + minimum = input_data.get("minimum") + + self.float_input.setDecimals(decimals) + if maximum is not None: + self.float_input.setMaximum(float(maximum)) + if minimum is not None: + self.float_input.setMinimum(float(minimum)) + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.float_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.float_input.setValue(value) + + self.origin_value = self.item_value() + + self.float_input.valueChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.float_input.setValue(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value(0) + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if not self._as_widget: + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.float_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + super(TextSingleLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QLineEdit() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setText(value) + + self.origin_value = self.item_value() + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.text_input.setText(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.text_input.text() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + super(TextMultiLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QPlainTextEdit() + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setPlainText(value) + + self.origin_value = self.item_value() + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.text_input.setPlainText(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal() + + def __init__(self, parent): + super(TextListItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.text_input = QtWidgets.QLineEdit() + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.text_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.text_input.textChanged.connect(self._on_value_change) + + self.is_single = False + + def _on_value_change(self): + self.value_changed.emit() + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.text_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + return self.text_input.text() + + +class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__(self, input_data, values, parent_keys, parent): + super(TextListSubWidget, self).__init__(parent) + self.setObjectName("TextListSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.add_row() + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.set_value(value) + + self.origin_value = self.item_value() + + def set_value(self, value, origin_value=False): + for input_field in self.input_fields: + self.remove_row(input_field) + + for item_text in value: + self.add_row(text=item_text) + + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value([]) + + def _on_value_change(self): + self.value_changed.emit() + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, text=None): + # Create new item + item_widget = TextListItem(self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set text if entered text is not None + # else (when add button clicked) trigger `_on_value_change` + if text is not None: + item_widget.text_input.setText(text) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def item_value(self): + output = [] + for item in self.input_fields: + text = item.config_value() + if text: + output.append(text) + + return output + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + super(TextListWidget, self).__init__(parent) + self.setObjectName("TextListWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + + self.label_widget = label_widget + # keys = list(parent_keys) + # keys.append(input_data["key"]) + # self.keys = keys + + self.value_widget = TextListSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + # self.value_widget.se + self.key = input_data["key"] + layout.addWidget(self.value_widget) + self.setLayout(layout) + + self.origin_value = self.item_value() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + def set_value(self, value, origin_value=False): + self.value_widget.set_value(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value([]) + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +class ExpandingWidget(QtWidgets.QWidget): + def __init__(self, label, parent): + super(ExpandingWidget, self).__init__(parent) + self.setObjectName("ExpandingWidget") + + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label_widget = QtWidgets.QLabel(label, parent=top_part) + label_widget.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(label_widget) + top_part.setLayout(layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.label_widget = label_widget + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + def set_content_widget(self, content_widget): + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget.setVisible(False) + + main_layout.addWidget(self.top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.content_widget = content_widget + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(ExpandingWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + +class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + if values is AS_WIDGET: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + self._parent = parent + + super(DictExpandWidget, self).__init__(parent) + self.setObjectName("DictExpandWidget") + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label = input_data["label"] + button_toggle_text = QtWidgets.QLabel(label, parent=top_part) + button_toggle_text.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(button_toggle_text) + top_part.setLayout(layout) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget = QtWidgets.QWidget(self) + content_widget.setVisible(False) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + + main_layout.addWidget(top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.button_toggle_text = button_toggle_text + + self.content_widget = content_widget + self.content_layout = content_layout + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + self._is_category = False + self._is_overriden = False + self.input_fields = [] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(DictExpandWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + @property + def is_category(self): + return self._is_category + + @property + def is_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + _is_modified = False + for input_field in self.input_fields: + if input_field.is_modified: + _is_modified = True + break + return _is_modified + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + @property + def is_overidable(self): + return self._parent.is_overidable + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + +class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + super(DictInvisible, self).__init__(parent) + self.setObjectName("DictInvisible") + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self._is_category = False + self._is_overriden = False + self.input_fields = [] + + if "key" not in input_data: + print(json.dumps(input_data, indent=4)) + + self.key = input_data["key"] + self.keys = list(parent_keys) + self.keys.append(self.key) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_category(self): + return self._is_category + + @property + def is_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + _is_modified = False + for input_field in self.input_fields: + if input_field.is_modified: + _is_modified = True + break + return _is_modified + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + if item_type == "schema": + for _schema in child_configuration["children"]: + children = config.gui_schema(_schema) + self.add_children_gui(children, values) + return + + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.layout().addWidget(item) + + self.input_fields.append(item) + return item + + +class DictFormWidget(QtWidgets.QWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + super(DictFormWidget, self).__init__(parent) + + self.input_fields = {} + self.content_layout = QtWidgets.QFormLayout(self) + + self.keys = list(parent_keys) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def item_value(self): + output = {} + for input_field in self.input_fields.values(): + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + @property + def is_overidable(self): + return self._parent.is_overidable + + def config_value(self): + return self.item_value() + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + key = child_configuration["key"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = QtWidgets.QLabel(label) + item = klass( + child_configuration, values, self.keys, self, label_widget + ) + self.content_layout.addRow(label_widget, item) + self.input_fields[key] = item + return item + + +class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal() + + def __init__(self, parent): + super(TextListItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.text_input = QtWidgets.QLineEdit() + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.text_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.text_input.textChanged.connect(self._on_value_change) + + self.is_single = False + + def _on_value_change(self): + self.value_changed.emit() + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.text_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + return self.text_input.text() + + +class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal() + + def __init__(self, object_type, parent): + self._parent = parent + + super(ModifiableDictItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + ItemKlass = TypeToKlass.types[object_type] + + self.key_input = QtWidgets.QLineEdit() + self.key_input.setObjectName("DictKey") + + self.value_input = ItemKlass( + {}, + AS_WIDGET, + [], + self, + None + ) + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.key_input, 0) + layout.addWidget(self.value_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.key_input.textChanged.connect(self._on_value_change) + self.value_input.value_changed.connect(self._on_value_change) + + self.origin_key = self._key() + self.origin_value = self.value_input.item_value() + + self.is_single = False + + def _key(self): + return self.key_input.text() + + def _on_value_change(self): + self._update_style() + self.value_changed.emit() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _update_style(self): + is_modified = self._key() != self.origin_key + # if self._is_overidable and self.is_overriden: + # if is_modified: + # state = "overriden-modified" + # else: + # state = "overriden" + if is_modified: + state = "modified" + else: + state = "original" + + self.key_input.setProperty("state", state) + self.key_input.style().polish(self.key_input) + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.value_input.clear_value() + self.key_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + if not key: + return {} + return {key: value} + + +class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__(self, input_data, values, parent_keys, parent): + self._parent = parent + + super(ModifiableDictSubWidget, self).__init__(parent) + self.setObjectName("ModifiableDictSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.object_type = input_data["object_type"] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_value) + + if self.count() == 0: + self.add_row() + + self.origin_value = self.config_value() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _on_value_change(self): + self.value_changed.emit() + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, key=None, value=None): + # Create new item + item_widget = ModifiableDictItem(self.object_type, self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set value if entered value is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None and key is not None: + item_widget.origin_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, origin_value=True) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def config_value(self): + output = {} + for item in self.input_fields: + item_value = item.config_value() + if item_value: + output.update(item_value) + return output + + +class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, + label_widget=None + ): + self._parent = parent + + super(ModifiableDict, self).__init__(input_data["label"], parent) + self.setObjectName("ModifiableDict") + + self.value_widget = ModifiableDictSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + self.set_content_widget(self.value_widget) + + self.key = input_data["key"] + + self.origin_value = self.item_value() + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +TypeToKlass.types["boolean"] = BooleanWidget +TypeToKlass.types["text-singleline"] = TextSingleLineWidget +TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["int"] = IntegerWidget +TypeToKlass.types["float"] = FloatWidget +TypeToKlass.types["dict-expanding"] = DictExpandWidget +TypeToKlass.types["dict-form"] = DictFormWidget +TypeToKlass.types["dict-invisible"] = DictInvisible +TypeToKlass.types["dict-modifiable"] = ModifiableDict +TypeToKlass.types["list-text"] = TextListWidget diff --git a/pype/tools/config_setting/widgets/lib.py b/pype/tools/config_setting/widgets/lib.py new file mode 100644 index 0000000000..ac0a353d53 --- /dev/null +++ b/pype/tools/config_setting/widgets/lib.py @@ -0,0 +1,44 @@ +import uuid + + +class CustomNone: + """Created object can be used as custom None (not equal to None). + + WARNING: Multiple created objects are not equal either. + Exmple: + >>> a = CustomNone() + >>> a == None + False + >>> b = CustomNone() + >>> a == b + False + >>> a == a + True + """ + + def __init__(self): + """Create uuid as identifier for custom None.""" + self.identifier = str(uuid.uuid4()) + + def __bool__(self): + """Return False (like default None).""" + return False + + def __eq__(self, other): + """Equality is compared by identifier value.""" + if type(other) == type(self): + if other.identifier == self.identifier: + return True + return False + + def __str__(self): + """Return value of identifier when converted to string.""" + return "".format(str(self.identifier)) + + def __repr__(self): + """Representation of custom None.""" + return "".format(str(self.identifier)) + + +NOT_SET = CustomNone() +AS_WIDGET = CustomNone() diff --git a/pype/tools/config_setting/widgets/main.py b/pype/tools/config_setting/widgets/main.py new file mode 100644 index 0000000000..af23e68f77 --- /dev/null +++ b/pype/tools/config_setting/widgets/main.py @@ -0,0 +1,26 @@ +from Qt import QtWidgets +from .base import StudioWidget, ProjectWidget + + +class MainWidget(QtWidgets.QWidget): + widget_width = 1000 + widget_height = 600 + + def __init__(self, parent=None): + super(MainWidget, self).__init__(parent) + + self.resize(self.widget_width, self.widget_height) + + header_tab_widget = QtWidgets.QTabWidget(parent=self) + + studio_widget = StudioWidget() + project_widget = ProjectWidget() + header_tab_widget.addTab(studio_widget, "Studio") + header_tab_widget.addTab(project_widget, "Project") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(header_tab_widget) + + self.setLayout(layout) diff --git a/pype/tools/config_setting/widgets/tests.py b/pype/tools/config_setting/widgets/tests.py new file mode 100644 index 0000000000..53b67de3a1 --- /dev/null +++ b/pype/tools/config_setting/widgets/tests.py @@ -0,0 +1,127 @@ +from Qt import QtWidgets, QtCore + + +class SelectableMenu(QtWidgets.QMenu): + + selection_changed = QtCore.Signal() + + def mouseReleaseEvent(self, event): + action = self.activeAction() + if action and action.isEnabled(): + action.trigger() + self.selection_changed.emit() + else: + super(SelectableMenu, self).mouseReleaseEvent(event) + + def event(self, event): + result = super(SelectableMenu, self).event(event) + if event.type() == QtCore.QEvent.Show: + parent = self.parent() + + move_point = parent.mapToGlobal(QtCore.QPoint(0, parent.height())) + check_point = ( + move_point + + QtCore.QPoint(self.width(), self.height()) + ) + visibility_check = ( + QtWidgets.QApplication.desktop().rect().contains(check_point) + ) + if not visibility_check: + move_point -= QtCore.QPoint(0, parent.height() + self.height()) + self.move(move_point) + + self.updateGeometry() + self.repaint() + + return result + + +class AddibleComboBox(QtWidgets.QComboBox): + """Searchable ComboBox with empty placeholder value as first value""" + + def __init__(self, placeholder="", parent=None): + super(AddibleComboBox, self).__init__(parent) + + self.setEditable(True) + # self.setInsertPolicy(self.NoInsert) + + self.lineEdit().setPlaceholderText(placeholder) + # self.lineEdit().returnPressed.connect(self.on_return_pressed) + + # Apply completer settings + completer = self.completer() + completer.setCompletionMode(completer.PopupCompletion) + completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) + + # def on_return_pressed(self): + # text = self.lineEdit().text().strip() + # if not text: + # return + # + # index = self.findText(text) + # if index < 0: + # self.addItems([text]) + # index = self.findText(text) + + + + def populate(self, items): + self.clear() + # self.addItems([""]) # ensure first item is placeholder + self.addItems(items) + + def get_valid_value(self): + """Return the current text if it's a valid value else None + + Note: The empty placeholder value is valid and returns as "" + + """ + + text = self.currentText() + lookup = set(self.itemText(i) for i in range(self.count())) + if text not in lookup: + return None + + return text or None + + +class MultiselectEnum(QtWidgets.QWidget): + + selection_changed = QtCore.Signal() + + def __init__(self, title, parent=None): + super(MultiselectEnum, self).__init__(parent) + toolbutton = QtWidgets.QToolButton(self) + toolbutton.setText(title) + + toolmenu = SelectableMenu(toolbutton) + + toolbutton.setMenu(toolmenu) + toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + + layout = QtWidgets.QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(toolbutton) + + self.setLayout(layout) + + toolmenu.selection_changed.connect(self.selection_changed) + + self.toolbutton = toolbutton + self.toolmenu = toolmenu + self.main_layout = layout + + def populate(self, items): + self.toolmenu.clear() + self.addItems(items) + + def addItems(self, items): + for item in items: + action = self.toolmenu.addAction(item) + action.setCheckable(True) + action.setChecked(True) + self.toolmenu.addAction(action) + + def items(self): + for action in self.toolmenu.actions(): + yield action From 899b654acec03e32eaac8e48df169f24a4232d24 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 11:48:27 +0200 Subject: [PATCH 140/947] added attribute is_group to be able recognize if key will be overriden on one subvalue change --- pype/tools/config_setting/widgets/inputs.py | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 1ddc27278d..02dd86c946 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1,10 +1,9 @@ +import json from Qt import QtWidgets, QtCore, QtGui from . import config from .base import PypeConfigurationWidget, TypeToKlass, ClickableWidget from .lib import NOT_SET, AS_WIDGET -import json - class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal() @@ -15,6 +14,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._as_widget = values is AS_WIDGET self._parent = parent + self.is_group = False + super(BooleanWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -129,6 +130,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + self.is_group = False + super(IntegerWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -220,6 +223,8 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + self.is_group = False + super(FloatWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -315,6 +320,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + self.is_group = False + super(TextSingleLineWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -398,6 +405,8 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_group = False + super(TextMultiLineWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -635,6 +644,9 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent + + self.is_group = False + super(TextListWidget, self).__init__(parent) self.setObjectName("TextListWidget") @@ -785,6 +797,8 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): )) self._parent = parent + self.is_group = input_data.get("is_group", False) + super(DictExpandWidget, self).__init__(parent) self.setObjectName("DictExpandWidget") top_part = ClickableWidget(parent=self) @@ -916,6 +930,8 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_group = input_data.get("is_group", False) + super(DictInvisible, self).__init__(parent) self.setObjectName("DictInvisible") @@ -993,6 +1009,8 @@ class DictFormWidget(QtWidgets.QWidget): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + self.is_group = input_data.get("is_group", False) + super(DictFormWidget, self).__init__(parent) self.input_fields = {} @@ -1288,6 +1306,8 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_group = input_data.get("is_group", False) + super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") From 908cccca120d9b372fb6f946148ed9496d1a6740 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 11:57:16 +0200 Subject: [PATCH 141/947] moved widgets out of the box to widgets.py --- pype/tools/config_setting/widgets/base.py | 14 --- pype/tools/config_setting/widgets/inputs.py | 99 ++--------------- pype/tools/config_setting/widgets/widgets.py | 105 +++++++++++++++++++ 3 files changed, 112 insertions(+), 106 deletions(-) create mode 100644 pype/tools/config_setting/widgets/widgets.py diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 0cc64a66de..3a495c6ae1 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -10,19 +10,6 @@ class TypeToKlass: types = {} -class ClickableWidget(QtWidgets.QLabel): - clicked = QtCore.Signal() - - def __init__(self, *args, **kwargs): - super(ClickableWidget, self).__init__(*args, **kwargs) - self.setObjectName("ExpandLabel") - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - self.clicked.emit() - super(ClickableWidget, self).mouseReleaseEvent(event) - - class PypeConfigurationWidget: is_category = False is_overriden = False @@ -197,7 +184,6 @@ class ProjectListWidget(QtWidgets.QWidget): # ) - class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): config_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 02dd86c946..7ef154ac9e 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1,7 +1,13 @@ import json from Qt import QtWidgets, QtCore, QtGui from . import config -from .base import PypeConfigurationWidget, TypeToKlass, ClickableWidget +from .base import PypeConfigurationWidget, TypeToKlass +from .widgets import ( + ClickableWidget, + ExpandingWidget, + ModifiedIntSpinBox, + ModifiedFloatSpinBox +) from .lib import NOT_SET, AS_WIDGET @@ -97,30 +103,6 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: self.item_value()} -class ModifiedIntSpinBox(QtWidgets.QSpinBox): - def __init__(self, *args, **kwargs): - super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - def wheelEvent(self, event): - if self.hasFocus(): - super(ModifiedIntSpinBox, self).wheelEvent(event) - else: - event.ignore() - - -class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): - def __init__(self, *args, **kwargs): - super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - - def wheelEvent(self, event): - if self.hasFocus(): - super(ModifiedFloatSpinBox, self).wheelEvent(event) - else: - event.ignore() - - class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal() @@ -720,73 +702,6 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: self.item_value()} -class ExpandingWidget(QtWidgets.QWidget): - def __init__(self, label, parent): - super(ExpandingWidget, self).__init__(parent) - self.setObjectName("ExpandingWidget") - - top_part = ClickableWidget(parent=self) - - button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) - button_toggle.setProperty("btn-type", "expand-toggle") - button_toggle.setIconSize(button_size) - button_toggle.setArrowType(QtCore.Qt.RightArrow) - button_toggle.setCheckable(True) - button_toggle.setChecked(False) - - label_widget = QtWidgets.QLabel(label, parent=top_part) - label_widget.setObjectName("ExpandLabel") - - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(label_widget) - top_part.setLayout(layout) - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - self.top_part = top_part - self.button_toggle = button_toggle - self.label_widget = label_widget - - self.top_part.clicked.connect(self._top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) - - def set_content_widget(self, content_widget): - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 9) - - content_widget.setVisible(False) - - main_layout.addWidget(self.top_part) - main_layout.addWidget(content_widget) - self.setLayout(main_layout) - - self.content_widget = content_widget - - def _top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) - - def toggle_content(self, *args): - if len(args) > 0: - checked = args[0] - else: - checked = self.button_toggle.isChecked() - arrow_type = QtCore.Qt.RightArrow - if checked: - arrow_type = QtCore.Qt.DownArrow - self.button_toggle.setChecked(checked) - self.button_toggle.setArrowType(arrow_type) - self.content_widget.setVisible(checked) - self.parent().updateGeometry() - - def resizeEvent(self, event): - super(ExpandingWidget, self).resizeEvent(event) - self.content_widget.updateGeometry() - - class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def __init__( self, input_data, values, parent_keys, parent, label_widget=None diff --git a/pype/tools/config_setting/widgets/widgets.py b/pype/tools/config_setting/widgets/widgets.py new file mode 100644 index 0000000000..34fdfde1a5 --- /dev/null +++ b/pype/tools/config_setting/widgets/widgets.py @@ -0,0 +1,105 @@ +from Qt import QtWidgets, QtCore + + +class ModifiedIntSpinBox(QtWidgets.QSpinBox): + def __init__(self, *args, **kwargs): + super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + super(ModifiedIntSpinBox, self).wheelEvent(event) + else: + event.ignore() + + +class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): + def __init__(self, *args, **kwargs): + super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + super(ModifiedFloatSpinBox, self).wheelEvent(event) + else: + event.ignore() + + +class ClickableWidget(QtWidgets.QLabel): + clicked = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(ClickableWidget, self).__init__(*args, **kwargs) + self.setObjectName("ExpandLabel") + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit() + super(ClickableWidget, self).mouseReleaseEvent(event) + + +class ExpandingWidget(QtWidgets.QWidget): + def __init__(self, label, parent): + super(ExpandingWidget, self).__init__(parent) + self.setObjectName("ExpandingWidget") + + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label_widget = QtWidgets.QLabel(label, parent=top_part) + label_widget.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(label_widget) + top_part.setLayout(layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.label_widget = label_widget + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + def set_content_widget(self, content_widget): + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget.setVisible(False) + + main_layout.addWidget(self.top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.content_widget = content_widget + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(ExpandingWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() From 63c13c655e2b41694055ef8940ba7824b95fd352 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 11:59:11 +0200 Subject: [PATCH 142/947] removed category attribute (as was replaced with group attribute) --- pype/tools/config_setting/widgets/base.py | 2 +- pype/tools/config_setting/widgets/inputs.py | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 3a495c6ae1..0fb4cd94cb 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -11,7 +11,7 @@ class TypeToKlass: class PypeConfigurationWidget: - is_category = False + is_group = False is_overriden = False is_modified = False diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 7ef154ac9e..64deb4d909 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -762,7 +762,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) - self._is_category = False self._is_overriden = False self.input_fields = [] @@ -794,10 +793,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): super(DictExpandWidget, self).resizeEvent(event) self.content_widget.updateGeometry() - @property - def is_category(self): - return self._is_category - @property def is_overriden(self): return self._is_overriden @@ -856,7 +851,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self._is_category = False self._is_overriden = False self.input_fields = [] @@ -874,10 +868,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): def is_overidable(self): return self._parent.is_overidable - @property - def is_category(self): - return self._is_category - @property def is_overriden(self): return self._is_overriden From c131ffefbdecf92c446ab1e00ced6051231ccfcb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Jul 2020 11:12:06 +0100 Subject: [PATCH 143/947] Calculate size - option to only calculate without deletion --- .../actions/action_delete_old_versions.py | 105 ++++++++++++++++-- 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delete_old_versions.py b/pype/modules/ftrack/actions/action_delete_old_versions.py index 46652b136a..6a4c5a0cae 100644 --- a/pype/modules/ftrack/actions/action_delete_old_versions.py +++ b/pype/modules/ftrack/actions/action_delete_old_versions.py @@ -105,11 +105,34 @@ class DeleteOldVersions(BaseAction): "value": False }) + items.append(self.splitter_item) + + items.append({ + "type": "label", + "value": ( + "This will NOT delete any files and only return the " + "total size of the files." + ) + }) + items.append({ + "type": "boolean", + "name": "only_calculate", + "label": "Only calculate size of files.", + "value": False + }) + return { "items": items, "title": self.inteface_title } + def sizeof_fmt(self, num, suffix='B'): + for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Yi', suffix) + def launch(self, session, entities, event): values = event["data"].get("values") if not values: @@ -117,6 +140,7 @@ class DeleteOldVersions(BaseAction): versions_count = int(values["last_versions_count"]) force_to_remove = values["force_delete_publish_folder"] + only_calculate = values["only_calculate"] _val1 = "OFF" if force_to_remove: @@ -318,10 +342,29 @@ class DeleteOldVersions(BaseAction): "Folder does not exist. Deleting it's files skipped: {}" ).format(paths_msg)) + # Size of files. + size = 0 + + if only_calculate: + if force_to_remove: + size = self.delete_whole_dir_paths( + dir_paths.values(), delete=False + ) + else: + size = self.delete_only_repre_files( + dir_paths, file_paths_by_dir, delete=False + ) + + msg = "Total size of files: " + self.sizeof_fmt(size) + + self.log.warning(msg) + + return {"success": True, "message": msg} + if force_to_remove: - self.delete_whole_dir_paths(dir_paths.values()) + size = self.delete_whole_dir_paths(dir_paths.values()) else: - self.delete_only_repre_files(dir_paths, file_paths_by_dir) + size = self.delete_only_repre_files(dir_paths, file_paths_by_dir) mongo_changes_bulk = [] for version in versions: @@ -383,17 +426,31 @@ class DeleteOldVersions(BaseAction): "message": msg } - return True + msg = "Total size of files deleted: " + self.sizeof_fmt(size) + + self.log.warning(msg) + + return {"success": True, "message": msg} + + def delete_whole_dir_paths(self, dir_paths, delete=True): + size = 0 - def delete_whole_dir_paths(self, dir_paths): for dir_path in dir_paths: # Delete all files and fodlers in dir path for root, dirs, files in os.walk(dir_path, topdown=False): for name in files: - os.remove(os.path.join(root, name)) + file_path = os.path.join(root, name) + size += os.path.getsize(file_path) + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) for name in dirs: - os.rmdir(os.path.join(root, name)) + if delete: + os.rmdir(os.path.join(root, name)) + + if not delete: + continue # Delete even the folder and it's parents folders if they are empty while True: @@ -406,7 +463,11 @@ class DeleteOldVersions(BaseAction): os.rmdir(os.path.join(dir_path)) - def delete_only_repre_files(self, dir_paths, file_paths): + return size + + def delete_only_repre_files(self, dir_paths, file_paths, delete=True): + size = 0 + for dir_id, dir_path in dir_paths.items(): dir_files = os.listdir(dir_path) collections, remainders = clique.assemble(dir_files) @@ -420,8 +481,13 @@ class DeleteOldVersions(BaseAction): "File was not found: {}".format(file_path) ) continue - os.remove(file_path) - self.log.debug("Removed file: {}".format(file_path)) + + size += os.path.getsize(file_path) + + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) + remainders.remove(file_path_base) continue @@ -440,21 +506,34 @@ class DeleteOldVersions(BaseAction): final_col.head = os.path.join(dir_path, final_col.head) for _file_path in final_col: if os.path.exists(_file_path): - os.remove(_file_path) + + size += os.path.getsize(_file_path) + + if delete: + os.remove(_file_path) + self.log.debug( + "Removed file: {}".format(_file_path) + ) + _seq_path = final_col.format("{head}{padding}{tail}") self.log.debug("Removed files: {}".format(_seq_path)) collections.remove(final_col) elif os.path.exists(file_path): - os.remove(file_path) - self.log.debug("Removed file: {}".format(file_path)) + size += os.path.getsize(file_path) + if delete: + os.remove(file_path) + self.log.debug("Removed file: {}".format(file_path)) else: self.log.warning( "File was not found: {}".format(file_path) ) # Delete as much as possible parent folders + if not delete: + return size + for dir_path in dir_paths.values(): while True: if not os.path.exists(dir_path): @@ -467,6 +546,8 @@ class DeleteOldVersions(BaseAction): self.log.debug("Removed folder: {}".format(dir_path)) os.rmdir(dir_path) + return size + def path_from_represenation(self, representation, anatomy): try: template = representation["data"]["template"] From 77c9725c595e4e40f25f0ad00d9ec4a4e70a7b9d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 14:14:28 +0200 Subject: [PATCH 144/947] store project's applications to event data to not query project all the time --- pype/modules/ftrack/lib/ftrack_app_handler.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index efc2df0ea5..b16dbd7b64 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -84,7 +84,7 @@ class AppAction(BaseAction): if ( len(entities) != 1 - or entities[0].entity_type.lower() != 'task' + or entities[0].entity_type.lower() != "task" ): return False @@ -92,21 +92,31 @@ class AppAction(BaseAction): if entity["parent"].entity_type.lower() == "project": return False - ft_project = self.get_project_from_entity(entity) - database = pypelib.get_avalon_database() - project_name = ft_project["full_name"] - avalon_project = database[project_name].find_one({ - "type": "project" - }) + avalon_project_apps = event["data"].get("avalon_project_apps", None) + avalon_project_doc = event["data"].get("avalon_project_doc", None) + if avalon_project_apps is None: + if avalon_project_doc is None: + ft_project = self.get_project_from_entity(entity) + database = pypelib.get_avalon_database() + project_name = ft_project["full_name"] + avalon_project_doc = database[project_name].find_one({ + "type": "project" + }) or False + event["data"]["avalon_project_doc"] = avalon_project_doc - if not avalon_project: + if not avalon_project_doc: + return False + + project_apps_config = avalon_project_doc["config"].get("apps", []) + avalon_project_apps = [ + app["name"] for app in project_apps_config + ] or False + event["data"]["avalon_project_apps"] = avalon_project_apps + + if not avalon_project_apps: return False - project_apps = avalon_project["config"].get("apps", []) - apps = [app["name"] for app in project_apps] - if self.identifier in apps: - return True - return False + return self.identifier in avalon_project_apps def _launch(self, event): entities = self._translate_event(event) From 7baa3bdd6a12732dcc62bc957c815c23c96b511a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 23 Jul 2020 14:55:07 +0200 Subject: [PATCH 145/947] support for single frame renders from maya --- .../global/publish/submit_publish_job.py | 38 +++++++++++++------ .../maya/publish/submit_maya_deadline.py | 35 ++++++++++++----- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 4f32e37c17..cec5f61011 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -380,15 +380,22 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # go through aovs in expected files for aov, files in exp_files[0].items(): cols, rem = clique.assemble(files) - # we shouldn't have any reminders - if rem: - self.log.warning( - "skipping unexpected files found " - "in sequence: {}".format(rem)) - - # but we really expect only one collection, nothing else make sense - assert len(cols) == 1, "only one image sequence type is expected" + # we shouldn't have any reminders. And if we do, it should + # be just one item for single frame renders. + if not cols and rem: + assert len(rem) == 1, ("Found multiple non related files " + "to render, don't know what to do " + "with them.") + col = rem[0] + _, ext = os.path.splitext(col) + else: + # but we really expect only one collection. + # Nothing else make sense. + assert len(cols) == 1, "only one image sequence type is expected" # noqa: E501 + _, ext = os.path.splitext(cols[0].tail) + col = list(cols[0]) + self.log.debug(col) # create subset name `familyTaskSubset_AOV` group_name = 'render{}{}{}{}'.format( task[0].upper(), task[1:], @@ -396,7 +403,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): subset_name = '{}_{}'.format(group_name, aov) - staging = os.path.dirname(list(cols[0])[0]) + if isinstance(col, (list, tuple)): + staging = os.path.dirname(col[0]) + else: + staging = os.path.dirname(col) + success, rootless_staging_dir = ( self.anatomy.find_root_template_from_path(staging) ) @@ -421,13 +432,16 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): new_instance["subset"] = subset_name new_instance["subsetGroup"] = group_name - ext = cols[0].tail.lstrip(".") - # create represenation + if isinstance(col, (list, tuple)): + files = [os.path.basename(f) for f in col] + else: + files = os.path.basename(col) + rep = { "name": ext, "ext": ext, - "files": [os.path.basename(f) for f in list(cols[0])], + "files": files, "frameStart": int(instance_data.get("frameStartHandle")), "frameEnd": int(instance_data.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index d81d43749c..d5500f7aa8 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -20,6 +20,7 @@ import os import json import getpass import copy +import re import clique import requests @@ -108,8 +109,8 @@ def get_renderer_variables(renderlayer, root): # does not work for vray. scene = cmds.file(query=True, sceneName=True) scene, _ = os.path.splitext(os.path.basename(scene)) - filename_0 = filename_prefix.replace('', scene) - filename_0 = filename_0.replace('', renderlayer) + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 filename_0 = "{}.{}.{}".format( filename_0, "#" * int(padding), extension) filename_0 = os.path.normpath(os.path.join(root, filename_0)) @@ -375,16 +376,32 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if isinstance(exp[0], dict): # we have aovs and we need to iterate over them for _aov, files in exp[0].items(): - col = clique.assemble(files)[0][0] - output_file = col.format('{head}{padding}{tail}') - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 + col, rem = clique.assemble(files) + if not col and rem: + # we couldn't find any collections but have + # individual files. + assert len(rem) == 1, ("Found multiple non related files " + "to render, don't know what to do " + "with them.") + payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 + output_file = rem[0] + else: + output_file = col.format('{head}{padding}{tail}') + payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 output_filenames[exp_index] = output_file exp_index += 1 else: - col = clique.assemble(files)[0][0] - output_file = col.format('{head}{padding}{tail}') - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file - # OutputFilenames[exp_index] = output_file + col, rem = clique.assemble(files) + if not col and rem: + # we couldn't find any collections but have + # individual files. + assert len(rem) == 1, ("Found multiple non related files " + "to render, don't know what to do " + "with them.") + payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 + else: + output_file = col.format('{head}{padding}{tail}') + payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 plugin = payload["JobInfo"]["Plugin"] self.log.info("using render plugin : {}".format(plugin)) From fc27d6d2dff908a41e3f45948f97d00f40770cb4 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 23 Jul 2020 15:27:37 +0200 Subject: [PATCH 146/947] render wasn't working without audio --- .../plugins/harmony/publish/extract_render.py | 23 ++++++++---- .../plugins/harmony/publish/validate_audio.py | 37 +++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 pype/plugins/harmony/publish/validate_audio.py diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 7ca83d3f0f..fe1352f9f9 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -111,13 +111,22 @@ class ExtractRender(pyblish.api.InstancePlugin): # Generate mov. mov_path = os.path.join(path, instance.data["name"] + ".mov") - args = [ - "ffmpeg", "-y", - "-i", audio_path, - "-i", - os.path.join(path, collection.head + "%04d" + collection.tail), - mov_path - ] + if os.path.isfile(audio_path): + args = [ + "ffmpeg", "-y", + "-i", audio_path, + "-i", + os.path.join(path, collection.head + "%04d" + collection.tail), + mov_path + ] + else: + args = [ + "ffmpeg", "-y", + "-i", + os.path.join(path, collection.head + "%04d" + collection.tail), + mov_path + ] + process = subprocess.Popen( args, stdout=subprocess.PIPE, diff --git a/pype/plugins/harmony/publish/validate_audio.py b/pype/plugins/harmony/publish/validate_audio.py new file mode 100644 index 0000000000..ba113e7610 --- /dev/null +++ b/pype/plugins/harmony/publish/validate_audio.py @@ -0,0 +1,37 @@ +import json +import os + +import pyblish.api + +import avalon.harmony +import pype.hosts.harmony + + +class ValidateAudio(pyblish.api.InstancePlugin): + """Ensures that there is an audio file in the scene. If you are sure that you want to send render without audio, you can disable this validator before clicking on "publish" """ + + order = pyblish.api.ValidatorOrder + label = "Validate Audio" + families = ["render"] + hosts = ["harmony"] + optional = True + + def process(self, instance): + # Collect scene data. + func = """function func(write_node) + { + return [ + sound.getSoundtrackAll().path() + ] + } + func + """ + result = avalon.harmony.send( + {"function": func, "args": [instance[0]]} + )["result"] + + audio_path = result[0] + + msg = "You are missing audio file:\n{}".format(audio_path) + + assert os.path.isfile(audio_path), msg From cef1e9435b8f39a01351ea6b5ecf215081c98461 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 23 Jul 2020 15:27:52 +0200 Subject: [PATCH 147/947] allow loading of workfiles as templates --- pype/plugins/harmony/load/load_template_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/harmony/load/load_template_workfile.py b/pype/plugins/harmony/load/load_template_workfile.py index b727cf865c..db67f20ff7 100644 --- a/pype/plugins/harmony/load/load_template_workfile.py +++ b/pype/plugins/harmony/load/load_template_workfile.py @@ -9,7 +9,7 @@ from avalon import api, harmony class ImportTemplateLoader(api.Loader): """Import templates.""" - families = ["harmony.template"] + families = ["harmony.template", "workfile"] representations = ["*"] label = "Import Template" From f184f71a0fbbe19b798ed7fa83e4dd77f208dd91 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 17:12:45 +0200 Subject: [PATCH 148/947] handle project view selection --- pype/tools/config_setting/widgets/base.py | 92 ++++++++++++++++------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 0fb4cd94cb..bd19873b5f 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -120,37 +120,80 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.content_layout.addWidget(item) +class ProjectListView(QtWidgets.QListView): + left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + index = self.indexAt(event.pos()) + self.left_mouse_released_at.emit(index) + super(ProjectListView, self).mouseReleaseEvent(event) + + class ProjectListWidget(QtWidgets.QWidget): default = "< Default >" - def __init__(self, parent=None): + def __init__(self, parent): + self._parent = parent + + self.current_project = None + super(ProjectListWidget, self).__init__(parent) - label = QtWidgets.QLabel("Project") - project_list = QtWidgets.QListView(self) + label_widget = QtWidgets.QLabel("Projects") + project_list = ProjectListView(self) project_list.setModel(QtGui.QStandardItemModel()) + # Do not allow editing + project_list.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) + # Do not automatically handle selection + project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + layout = QtWidgets.QVBoxLayout(self) - # content_margin = 5 - # layout.setContentsMargins( - # content_margin, - # content_margin, - # content_margin, - # content_margin - # ) - # layout.setSpacing(3) - layout.addWidget(label, 0) + layout.setSpacing(3) + layout.addWidget(label_widget, 0) layout.addWidget(project_list, 1) + project_list.left_mouse_released_at.connect(self.on_item_clicked) + self.project_list = project_list self.refresh() + def on_item_clicked(self, new_index): + new_project_name = new_index.data(QtCore.Qt.DisplayRole) + if new_project_name is None: + return + + if self.current_project == new_project_name: + return + + if self.validate_context_change(): + self.select_project(new_project_name) + self.current_project = new_project_name + + def validate_context_change(self): + # TODO add check if project can be changed (is modified) + return True + def project_name(self): - current_selection = self.project_list.currentText() - if current_selection == self.default: + if self.current_project == self.default: return None - return current_selection + return self.current_project + + def select_project(self, project_name): + model = self.project_list.model() + found_items = model.findItems(project_name) + if not found_items: + found_items = model.findItems(self.default) + + index = model.indexFromItem(found_items[0]) + self.project_list.selectionModel().clear() + self.project_list.selectionModel().setCurrentIndex( + index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent + ) def refresh(self): selected_project = None @@ -163,25 +206,16 @@ class ProjectListWidget(QtWidgets.QWidget): items = [self.default] io.install() for project_doc in tuple(io.projects()): - print(project_doc["name"]) items.append(project_doc["name"]) for item in items: model.appendRow(QtGui.QStandardItem(item)) - if not selected_project: - selected_project = self.default + self.select_project(selected_project) - found_items = model.findItems(selected_project) - if found_items: - index = model.indexFromItem(found_items[0]) - c = QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent - self.project_list.selectionModel().select( - index, c - ) - # self.project_list.selectionModel().setCurrentIndex( - # index, c - # ) + self.current_project = self.project_list.currentIndex().data( + QtCore.Qt.DisplayRole + ) class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): @@ -207,7 +241,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): scroll_widget.setWidgetResizable(True) scroll_widget.setWidget(content_widget) - project_list_widget = ProjectListWidget() + project_list_widget = ProjectListWidget(self) content_layout.addWidget(project_list_widget) self.project_list_widget = project_list_widget From 8437bf939292a03c5e58fd28c4f5631bc419121c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:39:52 +0100 Subject: [PATCH 149/947] correct render and publish render path passing --- .../celaction/publish/collect_render_path.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index d5fe6c07a5..a3918a52b6 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -10,9 +10,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.495 families = ["render.farm"] + # Presets + anatomy_render_key = None + anatomy_publish_render_key = None + def process(self, instance): anatomy = instance.context.data["anatomy"] anatomy_data = copy.deepcopy(instance.data["anatomyData"]) + anatomy_data["family"] = "render" padding = anatomy.templates.get("frame_padding", 4) anatomy_data.update({ "frame": f"%0{padding}d", @@ -21,12 +26,28 @@ class CollectRenderPath(pyblish.api.InstancePlugin): anatomy_filled = anatomy.format(anatomy_data) - render_dir = anatomy_filled["render_tmp"]["folder"] - render_path = anatomy_filled["render_tmp"]["path"] + # get anatomy rendering keys + anatomy_render_key = self.anatomy_render_key or "render" + anatomy_publish_render_key = self.anatomy_publish_render_key or "render" + + # get folder and path for rendering images from celaction + render_dir = anatomy_filled[anatomy_render_key]["folder"] + render_path = anatomy_filled[anatomy_render_key]["path"] # create dir if it doesnt exists - os.makedirs(render_dir, exist_ok=True) + try: + if not os.path.isdir(render_dir): + os.makedirs(render_dir, exist_ok=True) + except OSError: + # directory is not available + self.log.warning("Path is unreachable: `{}`".format(render_dir)) + # add rendering path to instance data instance.data["path"] = render_path + # get anatomy for published renders folder path + if anatomy_filled.get(anatomy_publish_render_key): + instance.data["publishRenderFolder"] = anatomy_filled[ + anatomy_publish_render_key]["folder"] + self.log.info(f"Render output path set to: `{render_path}`") From 7d49a281499f1ff01dc4258bd1b2b164bef13926 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:40:44 +0100 Subject: [PATCH 150/947] submit celaciton to deadline with consistent slashes in path --- pype/plugins/celaction/publish/submit_celaction_deadline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index c749ec111f..9091b24150 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -74,6 +74,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): resolution_width = instance.data["resolutionWidth"] resolution_height = instance.data["resolutionHeight"] render_dir = os.path.normpath(os.path.dirname(render_path)) + render_path = os.path.normpath(render_path) script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) @@ -98,6 +99,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): args = [ f"{script_path}", "-a", + "-16", "-s ", "-e ", f"-d {render_dir}", @@ -135,8 +137,10 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/") + "OutputFilename0": output_filename_0.replace("\\", "/"), + # # Asset dependency to wait for at least the scene file to sync. + # "AssetDependency0": script_path }, "PluginInfo": { # Input From 2e0880d90d33c6866c3fb2bdc80976823297eb60 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:41:26 +0100 Subject: [PATCH 151/947] fix(global): google drive stream file duplicity error --- pype/plugins/global/publish/integrate_new.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index d151cfc608..3582b55c82 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -551,12 +551,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # copy file with speedcopy and check if size of files are simetrical while True: + import shutil try: copyfile(src, dst) - except (OSError, AttributeError) as e: - self.log.warning(e) - # try it again with shutil - import shutil + except shutil.SameFileError as sfe: + self.log.critical("files are the same {} to {}".format(src, dst)) + os.remove(dst) try: shutil.copyfile(src, dst) self.log.debug("Copying files with shutil...") From ebe6bb4802a3ed39ab0ad00c6ad51e7c080cc255 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:42:12 +0100 Subject: [PATCH 152/947] fix(global): metadata file path with different anatomy key --- .../global/publish/submit_publish_job.py | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 4f32e37c17..f74b93cf7d 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -12,7 +12,15 @@ from avalon.vendor import requests, clique import pyblish.api -def _get_script(): +def _get_script(path): + + # pass input path if exists + if path: + if os.path.exists(path): + return str(path) + else: + raise + """Get path to the image sequence script.""" try: from pathlib import Path @@ -192,6 +200,38 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families_transfer = ["render3d", "render2d", "ftrack", "slate"] plugin_python_version = "3.7" + # script path for publish_filesequence.py + publishing_script = None + + def _create_metadata_path(self, instance): + ins_data = instance.data + # Ensure output dir exists + output_dir = ins_data.get("publishRenderFolder", ins_data["outputDir"]) + + try: + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + except OSError: + # directory is not available + self.log.warning("Path is unreachable: `{}`".format(output_dir)) + + metadata_filename = "{}_metadata.json".format(ins_data["subset"]) + + metadata_path = os.path.join(output_dir, metadata_filename) + + # Convert output dir to `{root}/rest/of/path/...` with Anatomy + success, roothless_mtdt_p = self.anatomy.find_root_template_from_path( + metadata_path) + if not success: + # `rootless_path` is not set to `output_dir` if none of roots match + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(output_dir)) + roothless_mtdt_p = metadata_path + + return (metadata_path, roothless_mtdt_p) + def _submit_deadline_post_job(self, instance, job): """Submit publish job to Deadline. @@ -205,17 +245,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): job_name = "Publish - {subset}".format(subset=subset) output_dir = instance.data["outputDir"] - # Convert output dir to `{root}/rest/of/path/...` with Anatomy - success, rootless_path = ( - self.anatomy.find_root_template_from_path(output_dir) - ) - if not success: - # `rootless_path` is not set to `output_dir` if none of roots match - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(output_dir)) - rootless_path = output_dir # Generate the payload for Deadline submission payload = { @@ -239,7 +268,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): }, "PluginInfo": { "Version": self.plugin_python_version, - "ScriptFile": _get_script(), + "ScriptFile": _get_script(self.publishing_script), "Arguments": "", "SingleFrameOnly": "True", }, @@ -249,11 +278,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Transfer the environment from the original job to this dependent # job so they use the same environment - metadata_filename = "{}_metadata.json".format(subset) - metadata_path = os.path.join(rootless_path, metadata_filename) + metadata_path, roothless_metadata_path = self._create_metadata_path( + instance) environment = job["Props"].get("Env", {}) - environment["PYPE_METADATA_FILE"] = metadata_path + environment["PYPE_METADATA_FILE"] = roothless_metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] environment["PYPE_LOG_NO_COLORS"] = "1" try: @@ -854,14 +883,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): } publish_job.update({"ftrack": ftrack}) - # Ensure output dir exists - output_dir = instance.data["outputDir"] - if not os.path.isdir(output_dir): - os.makedirs(output_dir) + metadata_path, roothless_metadata_path = self._create_metadata_path( + instance) - metadata_filename = "{}_metadata.json".format(subset) - - metadata_path = os.path.join(output_dir, metadata_filename) self.log.info("Writing json file: {}".format(metadata_path)) with open(metadata_path, "w") as f: json.dump(publish_job, f, indent=4, sort_keys=True) From 355d60c3b4b8224a680af956d693fd3be47c977d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 18:03:10 +0200 Subject: [PATCH 153/947] make sure inputs has is_modified and is_overriden not with metaclass --- pype/tools/config_setting/widgets/base.py | 4 ---- pype/tools/config_setting/widgets/inputs.py | 22 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index bd19873b5f..ebe15a370c 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -11,10 +11,6 @@ class TypeToKlass: class PypeConfigurationWidget: - is_group = False - is_overriden = False - is_modified = False - def config_value(self): raise NotImplementedError( "Method `config_value` is not implemented for `{}`.".format( diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 64deb4d909..1ba062ec82 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -20,7 +20,9 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._as_widget = values is AS_WIDGET self._parent = parent + self.is_modified = False self.is_group = False + self.is_overriden = False super(BooleanWidget, self).__init__(parent) @@ -112,7 +114,9 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + self.is_modified = False self.is_group = False + self.is_overriden = False super(IntegerWidget, self).__init__(parent) @@ -205,7 +209,9 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + self.is_modified = False self.is_group = False + self.is_overriden = False super(FloatWidget, self).__init__(parent) @@ -302,7 +308,9 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + self.is_modified = False self.is_group = False + self.is_overriden = False super(TextSingleLineWidget, self).__init__(parent) @@ -387,7 +395,9 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_modified = False self.is_group = False + self.is_overriden = False super(TextMultiLineWidget, self).__init__(parent) @@ -627,7 +637,9 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_modified = False self.is_group = False + self.is_overriden = False super(TextListWidget, self).__init__(parent) self.setObjectName("TextListWidget") @@ -712,6 +724,8 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): )) self._parent = parent + self.is_modified = False + self.is_overriden = False self.is_group = input_data.get("is_group", False) super(DictExpandWidget, self).__init__(parent) @@ -840,6 +854,8 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_modified = False + self.is_overriden = False self.is_group = input_data.get("is_group", False) super(DictInvisible, self).__init__(parent) @@ -914,7 +930,9 @@ class DictFormWidget(QtWidgets.QWidget): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): - self.is_group = input_data.get("is_group", False) + self.is_modified = False + self.is_overriden = False + self.is_group = False super(DictFormWidget, self).__init__(parent) @@ -1211,6 +1229,8 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): ): self._parent = parent + self.is_modified = False + self.is_overriden = False self.is_group = input_data.get("is_group", False) super(ModifiableDict, self).__init__(input_data["label"], parent) From 4d638ebdc85ff9077e79b49af2e38ee6fd344c2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 11:08:04 +0200 Subject: [PATCH 154/947] added item to tray menu with version --- pype/tools/tray/pype_tray.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 7dda8bf4f7..99832f0313 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -4,6 +4,11 @@ import platform from avalon import style from Qt import QtCore, QtGui, QtWidgets, QtSvg from pype.api import config, Logger, resources +import pype.version +try: + import configparser +except Exception: + import ConfigParser as configparser class TrayManager: @@ -100,6 +105,11 @@ class TrayManager: if items and self.services_submenu is not None: self.add_separator(self.tray_widget.menu) + version_string = self._version_string() + version_action = QtWidgets.QAction(version_string, self.tray_widget) + self.tray_widget.menu.addAction(version_action) + self.add_separator(self.tray_widget.menu) + # Add Exit action to menu aExit = QtWidgets.QAction("&Exit", self.tray_widget) aExit.triggered.connect(self.tray_widget.exit) @@ -109,6 +119,31 @@ class TrayManager: self.connect_modules() self.start_modules() + def _version_string(self): + subversion = None + client_name = None + config_file_path = os.path.join( + os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" + ) + version_string = pype.version.__version__ + if os.path.exists(config_file_path): + config = configparser.ConfigParser() + config.read(config_file_path) + try: + default_config = config["CLIENT"] + except Exception: + default_config = {} + subversion = default_config.get("subversion") + client_name = default_config.get("client_name") + + if subversion: + version_string += " ({})".format(subversion) + + if client_name: + version_string += ", {}".format(client_name) + + return version_string + def process_items(self, items, parent_menu): """ Loop through items and add them to parent_menu. From f4d49b330eabd9d9f6d5a744f1b9dd7374080df7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 11:12:47 +0200 Subject: [PATCH 155/947] add version item only if client information is in config.ini --- pype/tools/tray/pype_tray.py | 40 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 99832f0313..3d6d6d473b 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -105,10 +105,7 @@ class TrayManager: if items and self.services_submenu is not None: self.add_separator(self.tray_widget.menu) - version_string = self._version_string() - version_action = QtWidgets.QAction(version_string, self.tray_widget) - self.tray_widget.menu.addAction(version_action) - self.add_separator(self.tray_widget.menu) + self._add_version_item() # Add Exit action to menu aExit = QtWidgets.QAction("&Exit", self.tray_widget) @@ -119,30 +116,37 @@ class TrayManager: self.connect_modules() self.start_modules() - def _version_string(self): - subversion = None - client_name = None + def _add_version_item(self): config_file_path = os.path.join( os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" ) - version_string = pype.version.__version__ - if os.path.exists(config_file_path): - config = configparser.ConfigParser() - config.read(config_file_path) - try: - default_config = config["CLIENT"] - except Exception: - default_config = {} - subversion = default_config.get("subversion") - client_name = default_config.get("client_name") + if not os.path.exists(config_file_path): + return + subversion = None + client_name = None + + config = configparser.ConfigParser() + config.read(config_file_path) + try: + default_config = config["CLIENT"] + except Exception: + default_config = {} + subversion = default_config.get("subversion") + client_name = default_config.get("client_name") + if not subversion and not client_name: + return + + version_string = pype.version.__version__ if subversion: version_string += " ({})".format(subversion) if client_name: version_string += ", {}".format(client_name) - return version_string + version_action = QtWidgets.QAction(version_string, self.tray_widget) + self.tray_widget.menu.addAction(version_action) + self.add_separator(self.tray_widget.menu) def process_items(self, items, parent_menu): """ Loop through items and add them to parent_menu. From 60501b17632297ddd77d07c437c31cfc59da03ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 12:30:28 +0200 Subject: [PATCH 156/947] added time check for limit of 10 request in 1 second --- pype/modules/clockify/clockify_api.py | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 0a09c65628..d88b2ef8df 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -1,5 +1,6 @@ import os import re +import time import requests import json import datetime @@ -8,12 +9,27 @@ from .constants import ( ) +def time_check(obj): + if obj.request_counter < 10: + obj.request_counter += 1 + return + + wait_time = 1 - (time.time() - obj.request_time) + if wait_time > 0: + time.sleep(wait_time) + + obj.request_time = time.time() + obj.request_counter = 0 + + class ClockifyAPI: def __init__(self, api_key=None, master_parent=None): self.workspace_name = None self.workspace_id = None self.master_parent = master_parent self.api_key = api_key + self.request_counter = 0 + self.request_time = time.time() @property def headers(self): @@ -40,6 +56,7 @@ class ClockifyAPI: def validate_api_key(self, api_key): test_headers = {'X-Api-Key': api_key} action_url = 'workspaces/' + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=test_headers @@ -57,6 +74,7 @@ class ClockifyAPI: action_url = "/workspaces/{}/users/{}/permissions".format( workspace_id, user_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -69,6 +87,7 @@ class ClockifyAPI: def get_user_id(self): action_url = 'v1/user/' + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -129,6 +148,7 @@ class ClockifyAPI: def get_workspaces(self): action_url = 'workspaces/' + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -141,6 +161,7 @@ class ClockifyAPI: if workspace_id is None: workspace_id = self.workspace_id action_url = 'workspaces/{}/projects/'.format(workspace_id) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -156,6 +177,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/projects/{}/'.format( workspace_id, project_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -167,6 +189,7 @@ class ClockifyAPI: if workspace_id is None: workspace_id = self.workspace_id action_url = 'workspaces/{}/tags/'.format(workspace_id) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -182,6 +205,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/projects/{}/tasks/'.format( workspace_id, project_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -264,6 +288,7 @@ class ClockifyAPI: "taskId": task_id, "tagIds": tag_ids } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -281,6 +306,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/timeEntries/inProgress'.format( workspace_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -311,6 +337,7 @@ class ClockifyAPI: "tagIds": current["tagIds"], "end": self.get_current_time() } + time_check(self) response = requests.put( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -324,6 +351,7 @@ class ClockifyAPI: if workspace_id is None: workspace_id = self.workspace_id action_url = 'workspaces/{}/timeEntries/'.format(workspace_id) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -336,6 +364,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/timeEntries/{}'.format( workspace_id, tid ) + time_check(self) response = requests.delete( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -357,6 +386,7 @@ class ClockifyAPI: "color": "#f44336", "billable": "true" } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -367,6 +397,7 @@ class ClockifyAPI: def add_workspace(self, name): action_url = 'workspaces/' body = {"name": name} + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -386,6 +417,7 @@ class ClockifyAPI: "name": name, "projectId": project_id } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -400,6 +432,7 @@ class ClockifyAPI: body = { "name": name } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -415,6 +448,7 @@ class ClockifyAPI: action_url = '/workspaces/{}/projects/{}'.format( workspace_id, project_id ) + time_check(self) response = requests.delete( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, From 6b42c7bdc17763b84244fe1f4c7c06bc09e92b77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 15:22:23 +0200 Subject: [PATCH 157/947] child modified attribute added --- pype/tools/config_setting/style/style.css | 5 ++ pype/tools/config_setting/widgets/inputs.py | 87 +++++++++++++++------ 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css index f559a4c3b3..db46cc4c24 100644 --- a/pype/tools/config_setting/style/style.css +++ b/pype/tools/config_setting/style/style.css @@ -34,6 +34,7 @@ QLabel[state="overriden"] { } QWidget[input-state="original"] {} + QWidget[input-state="modified"] { border-color: #137cbd; } @@ -83,6 +84,10 @@ QPushButton[btn-type="expand-toggle"] { background: #1d272f; } +#ModifiableDict[state="child-modified"] { + border-color: #137cbd; +} + #TextListSubWidget { border: 1px solid #455c6e; border-radius: 3px; diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 1ba062ec82..9b24227a1d 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1,5 +1,5 @@ import json -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets, QtCore from . import config from .base import PypeConfigurationWidget, TypeToKlass from .widgets import ( @@ -67,6 +67,10 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.reset_value() + @property + def child_modified(self): + return self.is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -148,6 +152,10 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.int_input.valueChanged.connect(self._on_value_change) + @property + def child_modified(self): + return self.is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -253,6 +261,10 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.float_input.valueChanged.connect(self._on_value_change) + @property + def child_modified(self): + return self.is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -342,6 +354,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.text_input.textChanged.connect(self._on_value_change) + @property + def child_modified(self): + return self.is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -427,6 +443,10 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.text_input.textChanged.connect(self._on_value_change) + @property + def child_modified(self): + return self.is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -671,6 +691,10 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.origin_value = self.item_value() + @property + def child_modified(self): + return self.is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -808,17 +832,11 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.content_widget.updateGeometry() @property - def is_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - _is_modified = False + def child_modified(self): for input_field in self.input_fields: - if input_field.is_modified: - _is_modified = True - break - return _is_modified + if input_field.child_modified: + return True + return False def item_value(self): output = {} @@ -885,17 +903,11 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overidable @property - def is_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - _is_modified = False + def child_modified(self): for input_field in self.input_fields: - if input_field.is_modified: - _is_modified = True - break - return _is_modified + if input_field.child_modified: + return True + return False def item_value(self): output = {} @@ -930,6 +942,8 @@ class DictFormWidget(QtWidgets.QWidget): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + self._parent = parent + self.is_modified = False self.is_overriden = False self.is_group = False @@ -952,6 +966,13 @@ class DictFormWidget(QtWidgets.QWidget): output.update(input_field.config_value()) return output + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + @property def is_overidable(self): return self._parent.is_overidable @@ -1086,14 +1107,23 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def is_overidable(self): return self._parent.is_overidable + def is_key_modified(self): + return self._key() != self.origin_key + + def is_value_modified(self): + return self.value_input.is_modified + + @property + def is_modified(self): + return self.is_value_modified() or self.is_key_modified() + def _update_style(self): - is_modified = self._key() != self.origin_key # if self._is_overidable and self.is_overriden: # if is_modified: # state = "overriden-modified" # else: # state = "overriden" - if is_modified: + if self.is_key_modified(): state = "modified" else: state = "original" @@ -1230,6 +1260,7 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self._parent = parent self.is_modified = False + self.child_modified = False self.is_overriden = False self.is_group = input_data.get("is_group", False) @@ -1249,7 +1280,7 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.origin_value = self.item_value() def _on_value_change(self, value=None): - self.is_modified = self.item_value() != self.origin_value + self.child_modified = self.item_value() != self.origin_value self.is_overriden = True self._update_style() @@ -1259,6 +1290,14 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): return self._parent.is_overidable def _update_style(self): + if self.child_modified: + widget_state = "child-modified" + else: + widget_state = "" + + self.setProperty("state", widget_state) + self.style().polish(self) + if self.is_overidable and self.is_overriden: if self.is_modified: state = "overriden-modified" From 7058dc7d10312fb8c11dc4e45a0225634100dad0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 16:49:20 +0200 Subject: [PATCH 158/947] style fixes --- pype/tools/config_setting/style/style.css | 2 +- pype/tools/config_setting/widgets/base.py | 4 +- pype/tools/config_setting/widgets/inputs.py | 98 +++++++++++++++++---- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css index db46cc4c24..db2d746a23 100644 --- a/pype/tools/config_setting/style/style.css +++ b/pype/tools/config_setting/style/style.css @@ -84,7 +84,7 @@ QPushButton[btn-type="expand-toggle"] { background: #1d272f; } -#ModifiableDict[state="child-modified"] { +#DictExpandWidget[state="child-modified"], #ModifiableDict[state="child-modified"] { border-color: #137cbd; } diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index ebe15a370c..d2ccdddfc1 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -58,7 +58,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): scroll_widget = QtWidgets.QScrollArea(self) content_widget = QtWidgets.QWidget(scroll_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setContentsMargins(3, 3, 3, 3) content_layout.setSpacing(0) content_layout.setAlignment(QtCore.Qt.AlignTop) content_widget.setLayout(content_layout) @@ -229,7 +229,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): scroll_widget = QtWidgets.QScrollArea(self) content_widget = QtWidgets.QWidget(scroll_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setContentsMargins(3, 3, 3, 3) content_layout.setSpacing(0) content_layout.setAlignment(QtCore.Qt.AlignTop) content_widget.setLayout(content_layout) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 9b24227a1d..c2a6eff0cc 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -19,7 +19,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._as_widget = values is AS_WIDGET self._parent = parent - + print(10*"*", parent) + print(values) self.is_modified = False self.is_group = False self.is_overriden = False @@ -94,7 +95,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: state = "original" - if not self._as_widget: + if self._as_widget: property_name = "input-state" else: property_name = "state" @@ -290,19 +291,25 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit() def _update_style(self): - if not self._as_widget: - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" - elif self.is_modified: - state = "modified" + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" else: - state = "original" + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + if self._as_widget: + property_name = "input-state" + widget = self.float_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) def item_value(self): return self.float_input.value() @@ -393,8 +400,15 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: state = "original" - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) def item_value(self): return self.text_input.text() @@ -482,8 +496,15 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: state = "original" - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) def item_value(self): return self.text_input.toPlainText() @@ -652,6 +673,8 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): @@ -705,6 +728,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._update_style() + self.value_changed.emit() + def set_value(self, value, origin_value=False): self.value_widget.set_value(value) if origin_value: @@ -739,6 +764,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): @@ -831,6 +858,33 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): super(DictExpandWidget, self).resizeEvent(event) self.content_widget.updateGeometry() + def _on_value_change(self, value=None): + self.is_overriden = True + + self._update_style() + + def _update_style(self): + if self.child_modified: + widget_state = "child-modified" + else: + widget_state = "" + + self.setProperty("state", widget_state) + self.style().polish(self) + + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.button_toggle_text.setProperty("state", state) + self.button_toggle_text.style().polish(self.button_toggle_text) + @property def child_modified(self): for input_field in self.input_fields: @@ -860,6 +914,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): item = klass( child_configuration, values, self.keys, self ) + item.value_changed.connect(self._on_value_change) self.content_layout.addWidget(item) self.input_fields.append(item) @@ -939,6 +994,8 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): class DictFormWidget(QtWidgets.QWidget): + value_changed = QtCore.Signal() + def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): @@ -958,6 +1015,9 @@ class DictFormWidget(QtWidgets.QWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def _on_value_change(self): + self.value_changed.emit() + def item_value(self): output = {} for input_field in self.input_fields.values(): @@ -968,7 +1028,7 @@ class DictFormWidget(QtWidgets.QWidget): @property def child_modified(self): - for input_field in self.input_fields: + for input_field in self.input_fields.values(): if input_field.child_modified: return True return False @@ -989,9 +1049,11 @@ class DictFormWidget(QtWidgets.QWidget): klass = TypeToKlass.types.get(item_type) label_widget = QtWidgets.QLabel(label) + item = klass( child_configuration, values, self.keys, self, label_widget ) + item.value_changed.connect(self._on_value_change) self.content_layout.addRow(label_widget, item) self.input_fields[key] = item return item From 50422e3b057e4de54e56528497b8b1d671c87f48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 17:26:41 +0200 Subject: [PATCH 159/947] reset matching profiles if profile value is higher than previous --- pype/plugins/global/publish/integrate_new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index d151cfc608..6cb38c282c 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -740,6 +740,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): value += 1 if value > highest_value: + matching_profiles = {} highest_value = value if value == highest_value: From e083e9585912bf920475244a0096e6e8eb1a3c57 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 17:53:20 +0200 Subject: [PATCH 160/947] made it a little bit globally usable --- pype/tools/config_setting/interface.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/interface.py b/pype/tools/config_setting/interface.py index e95f3f5fe3..e227443c95 100644 --- a/pype/tools/config_setting/interface.py +++ b/pype/tools/config_setting/interface.py @@ -1,17 +1,26 @@ import os import sys -os.environ["PYPE_CONFIG"] = ( - "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype-config" + + +def folder_up(path, times=1): + if times <= 0: + return path + return folder_up(os.path.dirname(path), times - 1) + + +PYPE_SETUP_PATH = folder_up(__file__, 6) + +os.environ["PYPE_CONFIG"] = os.path.join( + PYPE_SETUP_PATH, "repos", "pype-config" ) os.environ["AVALON_MONGO"] = "mongodb://localhost:2707" sys_paths = ( "C:/Users/Public/pype_env2/Lib/site-packages", - "C:/Users/jakub.trllo/Desktop/pype/pype-setup", - "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype", - "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/avalon-core", - "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pyblish-base", - "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pyblish-lite", - "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype-config" + PYPE_SETUP_PATH, + os.path.join(PYPE_SETUP_PATH, "repos", "pype"), + os.path.join(PYPE_SETUP_PATH, "repos", "avalon-core"), + os.path.join(PYPE_SETUP_PATH, "repos", "pyblish-base"), + os.path.join(PYPE_SETUP_PATH, "repos", "pype-config"), ) for path in sys_paths: sys.path.append(path) From 8d92a455e6b2083df3f8476a838f7908b00d9f77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 18:07:45 +0200 Subject: [PATCH 161/947] minor tweaks in temp config files --- .../global/es/applications.json | 39 ------------------- .../config_gui_schema/studio_gui_schema.json | 15 ++++--- 2 files changed, 10 insertions(+), 44 deletions(-) delete mode 100644 pype/tools/config_setting/config/studio_presets/global/es/applications.json diff --git a/pype/tools/config_setting/config/studio_presets/global/es/applications.json b/pype/tools/config_setting/config/studio_presets/global/es/applications.json deleted file mode 100644 index 35e399444c..0000000000 --- a/pype/tools/config_setting/config/studio_presets/global/es/applications.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "blender_2.80": true, - "blender_2.81": true, - "blender_2.82": true, - "blender_2.83": true, - "harmony_17": true, - "houdini_16": true, - "houdini_17": true, - "houdini_18": true, - "maya_2016": true, - "maya_2017": true, - "maya_2018": true, - "maya_2019": true, - "maya_2020": true, - "nuke_10.0": true, - "nuke_11.0": true, - "nuke_11.2": true, - "nuke_11.3": true, - "nuke_12.0": true, - "nukex_10.0": true, - "nukex_11.0": true, - "nukex_11.2": true, - "nukex_11.3": true, - "nukex_12.0": true, - "nukestudio_10.0": true, - "nukestudio_11.0": true, - "nukestudio_11.2": true, - "nukestudio_11.3": true, - "nukestudio_12.0": true, - "photoshop_2020": true, - "premiere_2019": true, - "premiere_2020": true, - "python_2": true, - "python_3": true, - "resolve_16": true, - "shell": true, - "storyboardpro_7": true, - "unreal_4.21": true -} diff --git a/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json index 7d902bb8db..1a49735b8a 100644 --- a/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json @@ -4,11 +4,16 @@ "label": "Studio", "children": [ { - "type": "schema", - "children": [ - "applications_gui_schema", - "tools_gui_schema" - ] + "key": "global", + "type": "dict-invisible", + "label": "Global", + "children": [{ + "type": "schema", + "children": [ + "applications_gui_schema", + "tools_gui_schema" + ] + }] }, { "key": "muster", "type": "dict-invisible", From cbb77c05b10affef9a66a2bfb2284635adbaa71f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 18:07:57 +0200 Subject: [PATCH 162/947] preparation for studio save --- pype/tools/config_setting/widgets/base.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index d2ccdddfc1..337beb040e 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -80,10 +80,10 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) - btn = QtWidgets.QPushButton("Finish") + save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) - footer_layout.addWidget(btn, 0) + footer_layout.addWidget(save_btn, 0) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -93,9 +93,9 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): layout.addWidget(scroll_widget, 1) layout.addWidget(footer_widget, 0) - btn.clicked.connect(self.___finish) + save_btn.clicked.connect(self._save) - def ___finish(self): + def _save(self): output = {} for item in self.input_fields: output.update(item.config_value()) @@ -104,6 +104,9 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): _output = {key: output} output = _output + config_with_metadata = config.studio_presets_with_metadata() + + print(json.dumps(config_with_metadata, indent=4)) print(json.dumps(output, indent=4)) def add_children_gui(self, child_configuration, values): From 5ba9eb2eed39d05647d360621353d9a3a4ba3cb1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 18:08:35 +0200 Subject: [PATCH 163/947] paths are correct and metadata presets are with metadata --- pype/tools/config_setting/interface.py | 6 ++---- pype/tools/config_setting/widgets/config.py | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/interface.py b/pype/tools/config_setting/interface.py index e227443c95..a8c05f5af3 100644 --- a/pype/tools/config_setting/interface.py +++ b/pype/tools/config_setting/interface.py @@ -8,8 +8,7 @@ def folder_up(path, times=1): return folder_up(os.path.dirname(path), times - 1) -PYPE_SETUP_PATH = folder_up(__file__, 6) - +PYPE_SETUP_PATH = folder_up(os.path.realpath(__file__), 6) os.environ["PYPE_CONFIG"] = os.path.join( PYPE_SETUP_PATH, "repos", "pype-config" ) @@ -19,8 +18,7 @@ sys_paths = ( PYPE_SETUP_PATH, os.path.join(PYPE_SETUP_PATH, "repos", "pype"), os.path.join(PYPE_SETUP_PATH, "repos", "avalon-core"), - os.path.join(PYPE_SETUP_PATH, "repos", "pyblish-base"), - os.path.join(PYPE_SETUP_PATH, "repos", "pype-config"), + os.path.join(PYPE_SETUP_PATH, "repos", "pyblish-base") ) for path in sys_paths: sys.path.append(path) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 335299cb2f..58b1e03a25 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -160,10 +160,12 @@ def global_project_presets(**kwargs): def studio_presets_with_metadata(*args, **kwargs): + kwargs["with_metadata"] = True return load_jsons_from_dir(studio_presets_path, *args, **kwargs) def global_project_presets_with_metadata(**kwargs): + kwargs["with_metadata"] = True return load_jsons_from_dir(project_presets_path, **kwargs) From 5ba7a45235ca82486fe3db36774d4892aa3fedd4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 18:35:44 +0200 Subject: [PATCH 164/947] more close to saving studio presets --- pype/tools/config_setting/widgets/base.py | 42 ++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 337beb040e..a5f912d7cb 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -4,6 +4,7 @@ from Qt import QtWidgets, QtCore, QtGui from . import config from .lib import NOT_SET from avalon import io +from queue import Queue class TypeToKlass: @@ -96,18 +97,49 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): save_btn.clicked.connect(self._save) def _save(self): - output = {} + all_values = {} for item in self.input_fields: - output.update(item.config_value()) + all_values.update(item.config_value()) for key in reversed(self.keys): - _output = {key: output} - output = _output + _all_values = {key: all_values} + all_values = _all_values + # Skip first key + all_values = all_values["studio"] + + # Load studio data with metadata config_with_metadata = config.studio_presets_with_metadata() print(json.dumps(config_with_metadata, indent=4)) - print(json.dumps(output, indent=4)) + print(json.dumps(all_values, indent=4)) + + per_file_values = {} + process_queue = Queue() + for _key, _values in all_values.items(): + process_queue.put(( + config.studio_presets_path, _key, config_with_metadata, _values + )) + + while not process_queue.empty(): + path, key, metadata, values = process_queue.get() + new_path = os.path.join(path, key) + # TODO this should not be + if key in metadata: + key_metadata = metadata[key] + + if key_metadata["type"] == "file": + new_path += ".json" + per_file_values[new_path] = values + continue + + for new_key, new_values in values.items(): + process_queue.put( + (new_path, new_key, key_metadata["value"], new_values) + ) + + for path in per_file_values: + print(path) def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] From bd7876fe20acd526c56cdca408dd89805e7ce588 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 24 Jul 2020 19:52:20 +0200 Subject: [PATCH 165/947] bake custom attributes to camera during export --- .../maya/publish/extract_camera_alembic.py | 9 ++++ .../maya/publish/extract_camera_mayaAscii.py | 50 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/pype/plugins/maya/publish/extract_camera_alembic.py b/pype/plugins/maya/publish/extract_camera_alembic.py index cc090760ff..c61ec5e19e 100644 --- a/pype/plugins/maya/publish/extract_camera_alembic.py +++ b/pype/plugins/maya/publish/extract_camera_alembic.py @@ -19,6 +19,7 @@ class ExtractCameraAlembic(pype.api.Extractor): label = "Camera (Alembic)" hosts = ["maya"] families = ["camera"] + bake_attributes = [] def process(self, instance): @@ -66,6 +67,14 @@ class ExtractCameraAlembic(pype.api.Extractor): job_str += ' -file "{0}"'.format(path) + # bake specified attributes in preset + assert isinstance(self.bake_attributes, (list, tuple)), ( + "Attributes to bake must be specified as a list" + ) + for attr in self.bake_attributes: + self.log.info("Adding {} attribute".format(attr)) + job_str += " -attr {0}".format(attr) + with lib.evaluation("off"): with avalon.maya.suspended_refresh(): cmds.AbcExport(j=job_str, verbose=False) diff --git a/pype/plugins/maya/publish/extract_camera_mayaAscii.py b/pype/plugins/maya/publish/extract_camera_mayaAscii.py index 973d8d452a..eb3b1671de 100644 --- a/pype/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/pype/plugins/maya/publish/extract_camera_mayaAscii.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Extract camera to Maya file.""" import os from maya import cmds @@ -65,6 +67,45 @@ def unlock(plug): cmds.disconnectAttr(source, destination) +def bake_attribute(camera, + attributes, + step=1.0, simulation=True, frame_range=None): + """Bake specified attributes on camera. + + Args: + camera (str): Camera name. + attributes (list): List of attributes to bake. + step (float): Animation step used for baking. + simulation (bool): Perform simulation instead of just evaluating + each attribute separately over the range of time. + frame_rage (list, tuple): start and end frame to define range. + + .. See also: + http://download.autodesk.com/us/maya/2011help/Commandspython/bakeResults.html + + """ + + if frame_range is None: + frame_range = [cmds.playbackOptions(query=True, minTime=True), + cmds.playbackOptions(query=True, maxTime=True)] + + # If frame range is single frame bake one frame more, + # otherwise maya.cmds.bakeResults gets confused + if frame_range[1] == frame_range[0]: + frame_range[1] += 1 + + assert isinstance(attributes, (list, tuple)), ( + "Attributes to bake must be specified as a list" + ) + + with lib.keytangent_default(in_tangent_type='auto', + out_tangent_type='auto'): + cmds.bakeResults(camera, attribute=attributes, + simulation=simulation, + time=(frame_range[0], frame_range[1]), + sampleBy=step) + + class ExtractCameraMayaAscii(pype.api.Extractor): """Extract a Camera as Maya Ascii. @@ -84,6 +125,7 @@ class ExtractCameraMayaAscii(pype.api.Extractor): label = "Camera (Maya Ascii)" hosts = ["maya"] families = ["camera"] + bake_attributes = [] def process(self, instance): @@ -148,6 +190,14 @@ class ExtractCameraMayaAscii(pype.api.Extractor): unlock(plug) cmds.setAttr(plug, value) + if self.bake_attributes: + self.log.info( + "Baking attributes: {}".format( + self.bake_attributes)) + bake_attribute( + cam, self.bake_attributes, + frame_range=range_with_handles, step=step) + self.log.info("Performing extraction..") cmds.select(baked_shapes, noExpand=True) cmds.file(path, From 0c56ed576d2b12c41896dcc0ba436d1d549f30d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 01:21:20 +0200 Subject: [PATCH 166/947] logging gui has working filters and sorting with better widgets size on open --- pype/modules/logging/gui/app.py | 10 +- pype/modules/logging/gui/lib.py | 94 --------------- pype/modules/logging/gui/models.py | 179 +++++++++------------------- pype/modules/logging/gui/widgets.py | 79 +++++++----- 4 files changed, 106 insertions(+), 256 deletions(-) delete mode 100644 pype/modules/logging/gui/lib.py diff --git a/pype/modules/logging/gui/app.py b/pype/modules/logging/gui/app.py index 99b0b230a9..7827bdaf2e 100644 --- a/pype/modules/logging/gui/app.py +++ b/pype/modules/logging/gui/app.py @@ -8,9 +8,9 @@ class LogsWindow(QtWidgets.QWidget): super(LogsWindow, self).__init__(parent) self.setStyleSheet(style.load_stylesheet()) - self.resize(1200, 800) - logs_widget = LogsWidget(parent=self) + self.resize(1400, 800) log_detail = OutputWidget(parent=self) + logs_widget = LogsWidget(log_detail, parent=self) main_layout = QtWidgets.QHBoxLayout() @@ -18,8 +18,6 @@ class LogsWindow(QtWidgets.QWidget): log_splitter.setOrientation(QtCore.Qt.Horizontal) log_splitter.addWidget(logs_widget) log_splitter.addWidget(log_detail) - log_splitter.setStretchFactor(0, 65) - log_splitter.setStretchFactor(1, 35) main_layout.addWidget(log_splitter) @@ -33,5 +31,5 @@ class LogsWindow(QtWidgets.QWidget): def on_selection_changed(self): index = self.logs_widget.selected_log() - node = index.data(self.logs_widget.model.NodeRole) - self.log_detail.set_detail(node) + logs = index.data(self.logs_widget.model.ROLE_LOGS) + self.log_detail.set_detail(logs) diff --git a/pype/modules/logging/gui/lib.py b/pype/modules/logging/gui/lib.py deleted file mode 100644 index 85782e071e..0000000000 --- a/pype/modules/logging/gui/lib.py +++ /dev/null @@ -1,94 +0,0 @@ -import contextlib -from Qt import QtCore - - -def _iter_model_rows( - model, column, include_root=False -): - """Iterate over all row indices in a model""" - indices = [QtCore.QModelIndex()] # start iteration at root - - for index in indices: - # Add children to the iterations - child_rows = model.rowCount(index) - for child_row in range(child_rows): - child_index = model.index(child_row, column, index) - indices.append(child_index) - - if not include_root and not index.isValid(): - continue - - yield index - - -@contextlib.contextmanager -def preserve_states( - tree_view, column=0, role=None, - preserve_expanded=True, preserve_selection=True, - expanded_role=QtCore.Qt.DisplayRole, selection_role=QtCore.Qt.DisplayRole - -): - """Preserves row selection in QTreeView by column's data role. - - This function is created to maintain the selection status of - the model items. When refresh is triggered the items which are expanded - will stay expanded and vise versa. - - tree_view (QWidgets.QTreeView): the tree view nested in the application - column (int): the column to retrieve the data from - role (int): the role which dictates what will be returned - - Returns: - None - - """ - # When `role` is set then override both expanded and selection roles - if role: - expanded_role = role - selection_role = role - - model = tree_view.model() - selection_model = tree_view.selectionModel() - flags = selection_model.Select | selection_model.Rows - - expanded = set() - - if preserve_expanded: - for index in _iter_model_rows( - model, column=column, include_root=False - ): - if tree_view.isExpanded(index): - value = index.data(expanded_role) - expanded.add(value) - - selected = None - - if preserve_selection: - selected_rows = selection_model.selectedRows() - if selected_rows: - selected = set(row.data(selection_role) for row in selected_rows) - - try: - yield - finally: - if expanded: - for index in _iter_model_rows( - model, column=0, include_root=False - ): - value = index.data(expanded_role) - is_expanded = value in expanded - # skip if new index was created meanwhile - if is_expanded is None: - continue - tree_view.setExpanded(index, is_expanded) - - if selected: - # Go through all indices, select the ones with similar data - for index in _iter_model_rows( - model, column=column, include_root=False - ): - value = index.data(selection_role) - state = value in selected - if state: - tree_view.scrollTo(index) # Ensure item is visible - selection_model.select(index, flags) diff --git a/pype/modules/logging/gui/models.py b/pype/modules/logging/gui/models.py index ce1fa236a9..b739739b6f 100644 --- a/pype/modules/logging/gui/models.py +++ b/pype/modules/logging/gui/models.py @@ -1,21 +1,20 @@ import collections -from Qt import QtCore +from Qt import QtCore, QtGui from pype.api import Logger from pypeapp.lib.log import _bootstrap_mongo_log, LOG_COLLECTION_NAME log = Logger().get_logger("LogModel", "LoggingModule") -class LogModel(QtCore.QAbstractItemModel): - COLUMNS = [ +class LogModel(QtGui.QStandardItemModel): + COLUMNS = ( "process_name", "hostname", "hostip", "username", "system_name", "started" - ] - + ) colums_mapping = { "process_name": "Process Name", "process_id": "Process Id", @@ -25,30 +24,52 @@ class LogModel(QtCore.QAbstractItemModel): "system_name": "System name", "started": "Started at" } - process_keys = [ + process_keys = ( "process_id", "hostname", "hostip", "username", "system_name", "process_name" - ] - log_keys = [ + ) + log_keys = ( "timestamp", "level", "thread", "threadName", "message", "loggerName", "fileName", "module", "method", "lineNumber" - ] + ) default_value = "- Not set -" - NodeRole = QtCore.Qt.UserRole + 1 + + ROLE_LOGS = QtCore.Qt.UserRole + 2 def __init__(self, parent=None): super(LogModel, self).__init__(parent) - self._root_node = Node() + self.log_by_process = None self.dbcon = None + # Crash if connection is not possible to skip this module database = _bootstrap_mongo_log() if LOG_COLLECTION_NAME in database.list_collection_names(): self.dbcon = database[LOG_COLLECTION_NAME] - def add_log(self, log): - node = Node(log) - self._root_node.add_child(node) + def headerData(self, section, orientation, role): + if ( + role == QtCore.Qt.DisplayRole + and orientation == QtCore.Qt.Horizontal + ): + if section < len(self.COLUMNS): + key = self.COLUMNS[section] + return self.colums_mapping.get(key, key) + + super(LogModel, self).headerData(section, orientation, role) + + def add_process_logs(self, process_logs): + items = [] + first_item = True + for key in self.COLUMNS: + display_value = str(process_logs[key]) + item = QtGui.QStandardItem(display_value) + if first_item: + first_item = False + item.setData(process_logs["_logs"], self.ROLE_LOGS) + + items.append(item) + self.appendRow(items) def refresh(self): self.log_by_process = collections.defaultdict(list) @@ -65,16 +86,13 @@ class LogModel(QtCore.QAbstractItemModel): continue if process_id not in self.process_info: - proc_dict = {} + proc_dict = {"_logs": []} for key in self.process_keys: proc_dict[key] = ( item.get(key) or self.default_value ) self.process_info[process_id] = proc_dict - if "_logs" not in self.process_info[process_id]: - self.process_info[process_id]["_logs"] = [] - log_item = {} for key in self.log_keys: log_item[key] = item.get(key) or self.default_value @@ -89,114 +107,29 @@ class LogModel(QtCore.QAbstractItemModel): item["_logs"], key=lambda item: item["timestamp"] ) item["started"] = item["_logs"][0]["timestamp"] - self.add_log(item) + self.add_process_logs(item) self.endResetModel() - def data(self, index, role): - if not index.isValid(): - return None - if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole: - node = index.internalPointer() - column = index.column() +class LogsFilterProxy(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(LogsFilterProxy, self).__init__(*args, **kwargs) + self.col_usernames = None + self.filter_usernames = set() - key = self.COLUMNS[column] - if key == "started": - return str(node.get(key, None)) - return node.get(key, None) + def update_users_filter(self, users): + self.filter_usernames = set() + for user in users or tuple(): + self.filter_usernames.add(user) + self.invalidateFilter() - if role == self.NodeRole: - return index.internalPointer() - - def index(self, row, column, parent): - """Return index for row/column under parent""" - - if not parent.isValid(): - parent_node = self._root_node - else: - parent_node = parent.internalPointer() - - child_item = parent_node.child(row) - if child_item: - return self.createIndex(row, column, child_item) - return QtCore.QModelIndex() - - def rowCount(self, parent): - node = self._root_node - if parent.isValid(): - node = parent.internalPointer() - return node.childCount() - - def columnCount(self, parent): - return len(self.COLUMNS) - - def parent(self, index): - return QtCore.QModelIndex() - - def headerData(self, section, orientation, role): - if role == QtCore.Qt.DisplayRole: - if section < len(self.COLUMNS): - key = self.COLUMNS[section] - return self.colums_mapping.get(key, key) - - super(LogModel, self).headerData(section, orientation, role) - - def flags(self, index): - return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) - - def clear(self): - self.beginResetModel() - self._root_node = Node() - self.endResetModel() - - -class Node(dict): - """A node that can be represented in a tree view. - - The node can store data just like a dictionary. - - >>> data = {"name": "John", "score": 10} - >>> node = Node(data) - >>> assert node["name"] == "John" - - """ - - def __init__(self, data=None): - super(Node, self).__init__() - - self._children = list() - self._parent = None - - if data is not None: - assert isinstance(data, dict) - self.update(data) - - def childCount(self): - return len(self._children) - - def child(self, row): - if row >= len(self._children): - log.warning("Invalid row as child: {0}".format(row)) - return - - return self._children[row] - - def children(self): - return self._children - - def parent(self): - return self._parent - - def row(self): - """ - Returns: - int: Index of this node under parent""" - if self._parent is not None: - siblings = self.parent().children() - return siblings.index(self) - - def add_child(self, child): - """Add a child to this node""" - child._parent = self - self._children.append(child) + def filterAcceptsRow(self, source_row, source_parent): + if self.col_usernames is not None: + index = self.sourceModel().index( + source_row, self.col_usernames, source_parent + ) + user = index.data(QtCore.Qt.DisplayRole) + if user not in self.filter_usernames: + return False + return True diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index cf20066397..f567cae674 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -1,6 +1,6 @@ from Qt import QtCore, QtWidgets, QtGui from PyQt5.QtCore import QVariant -from .models import LogModel +from .models import LogModel, LogsFilterProxy class SearchComboBox(QtWidgets.QComboBox): @@ -193,54 +193,37 @@ class LogsWidget(QtWidgets.QWidget): active_changed = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, detail_widget, parent=None): super(LogsWidget, self).__init__(parent=parent) model = LogModel() + proxy_model = LogsFilterProxy() + proxy_model.setSourceModel(model) + proxy_model.col_usernames = model.COLUMNS.index("username") filter_layout = QtWidgets.QHBoxLayout() # user_filter = SearchComboBox(self, "Users") user_filter = CustomCombo("Users", self) - users = model.dbcon.distinct("user") + users = model.dbcon.distinct("username") user_filter.populate(users) user_filter.selection_changed.connect(self.user_changed) + proxy_model.update_users_filter(users) + level_filter = CustomCombo("Levels", self) # levels = [(level, True) for level in model.dbcon.distinct("level")] levels = model.dbcon.distinct("level") level_filter.addItems(levels) + level_filter.selection_changed.connect(self.level_changed) - date_from_label = QtWidgets.QLabel("From:") - date_filter_from = QtWidgets.QDateTimeEdit() - - date_from_layout = QtWidgets.QVBoxLayout() - date_from_layout.addWidget(date_from_label) - date_from_layout.addWidget(date_filter_from) - - # now = datetime.datetime.now() - # QtCore.QDateTime( - # now.year, - # now.month, - # now.day, - # now.hour, - # now.minute, - # second=0, - # msec=0, - # timeSpec=0 - # ) - date_to_label = QtWidgets.QLabel("To:") - date_filter_to = QtWidgets.QDateTimeEdit() - - date_to_layout = QtWidgets.QVBoxLayout() - date_to_layout.addWidget(date_to_label) - date_to_layout.addWidget(date_filter_to) + detail_widget.update_level_filter(levels) filter_layout.addWidget(user_filter) filter_layout.addWidget(level_filter) - filter_layout.addLayout(date_from_layout) - filter_layout.addLayout(date_to_layout) + spacer = QtWidgets.QWidget() + filter_layout.addWidget(spacer, 1) view = QtWidgets.QTreeView(self) view.setAllColumnsShowFocus(True) @@ -250,6 +233,8 @@ class LogsWidget(QtWidgets.QWidget): layout.addLayout(filter_layout) layout.addWidget(view) + view.setModel(proxy_model) + view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) view.setSortingEnabled(True) view.sortByColumn( @@ -257,24 +242,36 @@ class LogsWidget(QtWidgets.QWidget): QtCore.Qt.AscendingOrder ) - view.setModel(model) view.pressed.connect(self._on_activated) # prepare model.refresh() # Store to memory self.model = model + self.proxy_model = proxy_model self.view = view self.user_filter = user_filter self.level_filter = level_filter + self.detail_widget = detail_widget + def _on_activated(self, *args, **kwargs): self.active_changed.emit() def user_changed(self): + checked_values = set() for action in self.user_filter.items(): - print(action) + if action.isChecked(): + checked_values.add(action.text()) + self.proxy_model.update_users_filter(checked_values) + + def level_changed(self): + checked_values = set() + for action in self.level_filter.items(): + if action.isChecked(): + checked_values.add(action.text()) + self.detail_widget.update_level_filter(checked_values) def on_context_menu(self, point): # TODO will be any actions? it's ready @@ -309,13 +306,29 @@ class OutputWidget(QtWidgets.QWidget): self.setLayout(layout) self.output_text = output_text + self.las_logs = None + self.filter_levels = set() + + def update_level_filter(self, levels): + self.filter_levels = set() + for level in levels or tuple(): + self.filter_levels.add(level.lower()) + + self.set_detail(self.las_logs) + def add_line(self, line): self.output_text.append(line) - def set_detail(self, node): + def set_detail(self, logs): + self.las_logs = logs self.output_text.clear() - for log in node["_logs"]: + if not logs: + return + + for log in logs: level = log["level"].lower() + if level not in self.filter_levels: + continue line_f = "{message}" if level == "debug": From a6423c97120859916b61525ebc63e043e722fe7f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 01:35:28 +0200 Subject: [PATCH 167/947] added timestamp support --- pype/modules/logging/gui/widgets.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index f567cae674..aa044946cb 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -297,18 +297,32 @@ class OutputWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(OutputWidget, self).__init__(parent=parent) layout = QtWidgets.QVBoxLayout(self) + + show_timecode_checkbox = QtWidgets.QCheckBox("Show timestamp") + output_text = QtWidgets.QTextEdit() output_text.setReadOnly(True) # output_text.setLineWrapMode(QtWidgets.QTextEdit.FixedPixelWidth) + layout.addWidget(show_timecode_checkbox) layout.addWidget(output_text) + show_timecode_checkbox.stateChanged.connect( + self.on_show_timecode_change + ) self.setLayout(layout) self.output_text = output_text + self.show_timecode_checkbox = show_timecode_checkbox self.las_logs = None self.filter_levels = set() + def show_timecode(self): + return self.show_timecode_checkbox.isChecked() + + def on_show_timecode_change(self): + self.set_detail(self.las_logs) + def update_level_filter(self, levels): self.filter_levels = set() for level in levels or tuple(): @@ -325,6 +339,7 @@ class OutputWidget(QtWidgets.QWidget): if not logs: return + show_timecode = self.show_timecode() for log in logs: level = log["level"].lower() if level not in self.filter_levels: @@ -366,6 +381,10 @@ class OutputWidget(QtWidgets.QWidget): line = line_f.format(**log) + if show_timecode: + timestamp = log["timestamp"] + line = timestamp.strftime("%Y-%d-%m %H:%M:%S") + " " + line + self.add_line(line) if not exc: From 80a26eb9de57c42f0dc1873cc71e2417a1e398c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 01:59:09 +0200 Subject: [PATCH 168/947] removed unused --- pype/modules/logging/gui/widgets.py | 139 ---------------------------- 1 file changed, 139 deletions(-) diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index aa044946cb..51d3095b44 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -50,37 +50,6 @@ class SearchComboBox(QtWidgets.QComboBox): return text -class CheckableComboBox2(QtWidgets.QComboBox): - def __init__(self, parent=None): - super(CheckableComboBox, self).__init__(parent) - self.view().pressed.connect(self.handleItemPressed) - self._changed = False - - def handleItemPressed(self, index): - item = self.model().itemFromIndex(index) - if item.checkState() == QtCore.Qt.Checked: - item.setCheckState(QtCore.Qt.Unchecked) - else: - item.setCheckState(QtCore.Qt.Checked) - self._changed = True - - def hidePopup(self): - if not self._changed: - super(CheckableComboBox, self).hidePopup() - self._changed = False - - def itemChecked(self, index): - item = self.model().item(index, self.modelColumn()) - return item.checkState() == QtCore.Qt.Checked - - def setItemChecked(self, index, checked=True): - item = self.model().item(index, self.modelColumn()) - if checked: - item.setCheckState(QtCore.Qt.Checked) - else: - item.setCheckState(QtCore.Qt.Unchecked) - - class SelectableMenu(QtWidgets.QMenu): selection_changed = QtCore.Signal() @@ -137,57 +106,6 @@ class CustomCombo(QtWidgets.QWidget): yield action -class CheckableComboBox(QtWidgets.QComboBox): - def __init__(self, parent=None): - super(CheckableComboBox, self).__init__(parent) - - view = QtWidgets.QTreeView() - view.header().hide() - view.setRootIsDecorated(False) - - model = QtGui.QStandardItemModel() - - view.pressed.connect(self.handleItemPressed) - self._changed = False - - self.setView(view) - self.setModel(model) - - self.view = view - self.model = model - - def handleItemPressed(self, index): - item = self.model.itemFromIndex(index) - if item.checkState() == QtCore.Qt.Checked: - item.setCheckState(QtCore.Qt.Unchecked) - else: - item.setCheckState(QtCore.Qt.Checked) - self._changed = True - - def hidePopup(self): - if not self._changed: - super(CheckableComboBox, self).hidePopup() - self._changed = False - - def itemChecked(self, index): - item = self.model.item(index, self.modelColumn()) - return item.checkState() == QtCore.Qt.Checked - - def setItemChecked(self, index, checked=True): - item = self.model.item(index, self.modelColumn()) - if checked: - item.setCheckState(QtCore.Qt.Checked) - else: - item.setCheckState(QtCore.Qt.Unchecked) - - def addItems(self, items): - for text, checked in items: - text_item = QtGui.QStandardItem(text) - checked_item = QtGui.QStandardItem() - checked_item.setData(QVariant(checked), QtCore.Qt.CheckStateRole) - self.model.appendRow([text_item, checked_item]) - - class LogsWidget(QtWidgets.QWidget): """A widget that lists the published subsets for an asset""" @@ -391,60 +309,3 @@ class OutputWidget(QtWidgets.QWidget): continue for _line in exc["stackTrace"].split("\n"): self.add_line(_line) - - -class LogDetailWidget(QtWidgets.QWidget): - """A Widget that display information about a specific version""" - data_rows = [ - "user", - "message", - "level", - "logname", - "method", - "module", - "fileName", - "lineNumber", - "host", - "timestamp" - ] - - html_text = u""" -

{user} - {timestamp}

-User
{user}
-
Level
{level}
-
Message
{message}
-
Log Name
{logname}

Method
{method}
-
File
{fileName}
-
Line
{lineNumber}
-
Host
{host}
-
Timestamp
{timestamp}
-""" - - def __init__(self, parent=None): - super(LogDetailWidget, self).__init__(parent=parent) - - layout = QtWidgets.QVBoxLayout(self) - - label = QtWidgets.QLabel("Detail") - detail_widget = QtWidgets.QTextEdit() - detail_widget.setReadOnly(True) - layout.addWidget(label) - layout.addWidget(detail_widget) - - self.detail_widget = detail_widget - - self.setEnabled(True) - - self.set_detail(None) - - def set_detail(self, detail_data): - if not detail_data: - self.detail_widget.setText("") - return - - data = dict() - for row in self.data_rows: - value = detail_data.get(row) or "< Not set >" - data[row] = value - - self.detail_widget.setHtml(self.html_text.format(**data)) From 5c57e183b0efc64e4af1f49bc116e7daf59bf14a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 01:59:35 +0200 Subject: [PATCH 169/947] added refresh --- pype/modules/logging/gui/app.py | 7 ---- pype/modules/logging/gui/models.py | 3 +- pype/modules/logging/gui/widgets.py | 50 +++++++++++++++++++---------- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/pype/modules/logging/gui/app.py b/pype/modules/logging/gui/app.py index 7827bdaf2e..c0e180c8a1 100644 --- a/pype/modules/logging/gui/app.py +++ b/pype/modules/logging/gui/app.py @@ -26,10 +26,3 @@ class LogsWindow(QtWidgets.QWidget): self.setLayout(main_layout) self.setWindowTitle("Logs") - - self.logs_widget.active_changed.connect(self.on_selection_changed) - - def on_selection_changed(self): - index = self.logs_widget.selected_log() - logs = index.data(self.logs_widget.model.ROLE_LOGS) - self.log_detail.set_detail(logs) diff --git a/pype/modules/logging/gui/models.py b/pype/modules/logging/gui/models.py index b739739b6f..ae2666f501 100644 --- a/pype/modules/logging/gui/models.py +++ b/pype/modules/logging/gui/models.py @@ -35,6 +35,7 @@ class LogModel(QtGui.QStandardItemModel): default_value = "- Not set -" ROLE_LOGS = QtCore.Qt.UserRole + 2 + ROLE_PROCESS_ID = QtCore.Qt.UserRole + 3 def __init__(self, parent=None): super(LogModel, self).__init__(parent) @@ -67,7 +68,7 @@ class LogModel(QtGui.QStandardItemModel): if first_item: first_item = False item.setData(process_logs["_logs"], self.ROLE_LOGS) - + item.setData(process_logs["process_id"], self.ROLE_PROCESS_ID) items.append(item) self.appendRow(items) diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index 51d3095b44..5304fc4d56 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -1,5 +1,5 @@ -from Qt import QtCore, QtWidgets, QtGui -from PyQt5.QtCore import QVariant +from Qt import QtCore, QtWidgets +from avalon.vendor import qtawesome from .models import LogModel, LogsFilterProxy @@ -109,8 +109,6 @@ class CustomCombo(QtWidgets.QWidget): class LogsWidget(QtWidgets.QWidget): """A widget that lists the published subsets for an asset""" - active_changed = QtCore.Signal() - def __init__(self, detail_widget, parent=None): super(LogsWidget, self).__init__(parent=parent) @@ -125,7 +123,7 @@ class LogsWidget(QtWidgets.QWidget): user_filter = CustomCombo("Users", self) users = model.dbcon.distinct("username") user_filter.populate(users) - user_filter.selection_changed.connect(self.user_changed) + user_filter.selection_changed.connect(self._user_changed) proxy_model.update_users_filter(users) @@ -133,15 +131,19 @@ class LogsWidget(QtWidgets.QWidget): # levels = [(level, True) for level in model.dbcon.distinct("level")] levels = model.dbcon.distinct("level") level_filter.addItems(levels) - level_filter.selection_changed.connect(self.level_changed) + level_filter.selection_changed.connect(self._level_changed) detail_widget.update_level_filter(levels) + spacer = QtWidgets.QWidget() + + icon = qtawesome.icon("fa.refresh", color="white") + refresh_btn = QtWidgets.QPushButton(icon, "") + filter_layout.addWidget(user_filter) filter_layout.addWidget(level_filter) - - spacer = QtWidgets.QWidget() filter_layout.addWidget(spacer, 1) + filter_layout.addWidget(refresh_btn) view = QtWidgets.QTreeView(self) view.setAllColumnsShowFocus(True) @@ -160,9 +162,8 @@ class LogsWidget(QtWidgets.QWidget): QtCore.Qt.AscendingOrder ) - view.pressed.connect(self._on_activated) - # prepare - model.refresh() + view.selectionModel().selectionChanged.connect(self._on_index_change) + refresh_btn.clicked.connect(self._on_refresh_clicked) # Store to memory self.model = model @@ -173,18 +174,33 @@ class LogsWidget(QtWidgets.QWidget): self.level_filter = level_filter self.detail_widget = detail_widget + self.refresh_btn = refresh_btn - def _on_activated(self, *args, **kwargs): - self.active_changed.emit() + # prepare + self.refresh() - def user_changed(self): + def refresh(self): + self.model.refresh() + + def _on_refresh_clicked(self): + self.refresh() + + def _on_index_change(self, to_index, from_index): + index = self._selected_log() + if index: + logs = index.data(self.model.ROLE_LOGS) + else: + logs = [] + self.detail_widget.set_detail(logs) + + def _user_changed(self): checked_values = set() for action in self.user_filter.items(): if action.isChecked(): checked_values.add(action.text()) self.proxy_model.update_users_filter(checked_values) - def level_changed(self): + def _level_changed(self): checked_values = set() for action in self.level_filter.items(): if action.isChecked(): @@ -203,7 +219,7 @@ class LogsWidget(QtWidgets.QWidget): selection = self.view.selectionModel() rows = selection.selectedRows(column=0) - def selected_log(self): + def _selected_log(self): selection = self.view.selectionModel() rows = selection.selectedRows(column=0) if len(rows) == 1: @@ -251,7 +267,7 @@ class OutputWidget(QtWidgets.QWidget): def add_line(self, line): self.output_text.append(line) - def set_detail(self, logs): + def set_detail(self, logs=None): self.las_logs = logs self.output_text.clear() if not logs: From 862a07ac4f9b513ab20b1d613709497dfad5afb6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 02:09:37 +0200 Subject: [PATCH 170/947] refresh enhancement --- pype/modules/logging/gui/widgets.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index 5304fc4d56..826f32646d 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -181,6 +181,7 @@ class LogsWidget(QtWidgets.QWidget): def refresh(self): self.model.refresh() + self.detail_widget.refresh() def _on_refresh_clicked(self): self.refresh() @@ -248,8 +249,10 @@ class OutputWidget(QtWidgets.QWidget): self.output_text = output_text self.show_timecode_checkbox = show_timecode_checkbox - self.las_logs = None - self.filter_levels = set() + self.refresh() + + def refresh(self): + self.set_detail() def show_timecode(self): return self.show_timecode_checkbox.isChecked() From 5b307448c99aaea8ab41cf3261f8b83a552ea317 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 13:41:44 +0200 Subject: [PATCH 171/947] disable editing --- pype/modules/logging/gui/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index 826f32646d..9b6c0a6a62 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -147,6 +147,7 @@ class LogsWidget(QtWidgets.QWidget): view = QtWidgets.QTreeView(self) view.setAllColumnsShowFocus(True) + view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) From 04e6c06ad1bde4d3375a230f416f1b6e5d888f96 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 26 Jul 2020 13:44:28 +0200 Subject: [PATCH 172/947] reverse sort order --- pype/modules/logging/gui/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/logging/gui/widgets.py b/pype/modules/logging/gui/widgets.py index 9b6c0a6a62..cd0df283bf 100644 --- a/pype/modules/logging/gui/widgets.py +++ b/pype/modules/logging/gui/widgets.py @@ -160,7 +160,7 @@ class LogsWidget(QtWidgets.QWidget): view.setSortingEnabled(True) view.sortByColumn( model.COLUMNS.index("started"), - QtCore.Qt.AscendingOrder + QtCore.Qt.DescendingOrder ) view.selectionModel().selectionChanged.connect(self._on_index_change) From b7cf6679248a169f8bff0323a48014cb0e139433 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 10:03:22 +0200 Subject: [PATCH 173/947] show the version string all the time --- pype/tools/tray/pype_tray.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 3d6d6d473b..9537b62581 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -120,22 +120,18 @@ class TrayManager: config_file_path = os.path.join( os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" ) - if not os.path.exists(config_file_path): - return - subversion = None - client_name = None + default_config = {} + if os.path.exists(config_file_path): + config = configparser.ConfigParser() + config.read(config_file_path) + try: + default_config = config["CLIENT"] + except Exception: + pass - config = configparser.ConfigParser() - config.read(config_file_path) - try: - default_config = config["CLIENT"] - except Exception: - default_config = {} subversion = default_config.get("subversion") client_name = default_config.get("client_name") - if not subversion and not client_name: - return version_string = pype.version.__version__ if subversion: From 22adf6f9e3ffb3dfe2891f0bc23198a112babbe6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 10:51:17 +0200 Subject: [PATCH 174/947] fix dictinary value changed --- pype/tools/config_setting/widgets/inputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index c2a6eff0cc..01a22bd702 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -861,6 +861,8 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_overriden = True + self.value_changed.emit() + self._update_style() def _update_style(self): From a47ffde54203adcff6b87cf2d379ab402fea4fb1 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 27 Jul 2020 11:05:32 +0200 Subject: [PATCH 175/947] sanitize camera names --- pype/hosts/maya/expected_files.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pype/hosts/maya/expected_files.py b/pype/hosts/maya/expected_files.py index a7204cba93..77d55eb1c1 100644 --- a/pype/hosts/maya/expected_files.py +++ b/pype/hosts/maya/expected_files.py @@ -158,6 +158,25 @@ class AExpectedFiles: """To be implemented by renderer class.""" pass + def sanitize_camera_name(self, camera): + """Sanitize camera name. + + Remove Maya illegal characters from camera name. + + Args: + camera (str): Maya camera name. + + Returns: + (str): sanitized camera name + + Example: + >>> sanizite_camera_name('test:camera_01') + test_camera_01 + + """ + sanitized = re.sub('[^0-9a-zA-Z_]+', '_', camera) + return sanitized + def get_renderer_prefix(self): """Return prefix for specific renderer. @@ -252,7 +271,7 @@ class AExpectedFiles: mappings = ( (R_SUBSTITUTE_SCENE_TOKEN, layer_data["sceneName"]), (R_SUBSTITUTE_LAYER_TOKEN, layer_data["layerName"]), - (R_SUBSTITUTE_CAMERA_TOKEN, cam), + (R_SUBSTITUTE_CAMERA_TOKEN, self.sanitize_camera_name(cam)), # this is required to remove unfilled aov token, for example # in Redshift (R_REMOVE_AOV_TOKEN, ""), @@ -287,7 +306,8 @@ class AExpectedFiles: mappings = ( (R_SUBSTITUTE_SCENE_TOKEN, layer_data["sceneName"]), (R_SUBSTITUTE_LAYER_TOKEN, layer_data["layerName"]), - (R_SUBSTITUTE_CAMERA_TOKEN, cam), + (R_SUBSTITUTE_CAMERA_TOKEN, + self.sanitize_camera_name(cam)), (R_SUBSTITUTE_AOV_TOKEN, aov[0]), (R_CLEAN_FRAME_TOKEN, ""), (R_CLEAN_EXT_TOKEN, ""), @@ -314,7 +334,8 @@ class AExpectedFiles: # camera name to AOV to allow per camera AOVs. aov_name = aov[0] if len(layer_data["cameras"]) > 1: - aov_name = "{}_{}".format(aov[0], cam) + aov_name = "{}_{}".format(aov[0], + self.sanitize_camera_name(cam)) aov_file_list[aov_name] = aov_files file_prefix = layer_data["filePrefix"] From 4d52556f2b4cbbbe7b4c9c28944d4c88d877bab3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 11:14:56 +0200 Subject: [PATCH 176/947] fix values keys --- pype/tools/config_setting/widgets/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index a5f912d7cb..b4ad206076 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -130,7 +130,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): if key_metadata["type"] == "file": new_path += ".json" - per_file_values[new_path] = values + per_file_values[new_path] = {key: values} continue for new_key, new_values in values.items(): From ce9f4315086b039ca0eb7b3b2ae44a09df2d32b7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:04:48 +0200 Subject: [PATCH 177/947] saving studio files --- pype/tools/config_setting/widgets/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index b4ad206076..b68ec4126a 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -130,7 +130,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): if key_metadata["type"] == "file": new_path += ".json" - per_file_values[new_path] = {key: values} + per_file_values[new_path] = values continue for new_key, new_values in values.items(): @@ -138,8 +138,9 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): (new_path, new_key, key_metadata["value"], new_values) ) - for path in per_file_values: - print(path) + for file_path, file_values in per_file_values.items(): + with open(file_path, "w") as file_stream: + json.dump(file_values, file_stream, indent=4) def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] From 702ad110235885020a3291a58e3602f51639ab26 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 27 Jul 2020 12:13:45 +0200 Subject: [PATCH 178/947] fix(celaction): version up --- pype/plugins/celaction/publish/integrate_version_up.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/celaction/publish/integrate_version_up.py b/pype/plugins/celaction/publish/integrate_version_up.py index 7fb1efa8aa..1822ceabcb 100644 --- a/pype/plugins/celaction/publish/integrate_version_up.py +++ b/pype/plugins/celaction/publish/integrate_version_up.py @@ -4,9 +4,9 @@ import pyblish.api class VersionUpScene(pyblish.api.ContextPlugin): - order = pyblish.api.IntegratorOrder + order = pyblish.api.IntegratorOrder + 0.5 label = 'Version Up Scene' - families = ['scene'] + families = ['workfile'] optional = True active = True From f52a1de2d1ccb803b929c7b2e9fac5b1806a565e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:25:38 +0200 Subject: [PATCH 179/947] fix stored project entity in collect ftrack api --- pype/plugins/ftrack/publish/collect_ftrack_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index 151b8882a3..bbda6da3b0 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -96,6 +96,6 @@ class CollectFtrackApi(pyblish.api.ContextPlugin): task_entity = None self.log.warning("Task name is not set.") - context.data["ftrackProject"] = asset_entity + context.data["ftrackProject"] = project_entity context.data["ftrackEntity"] = asset_entity context.data["ftrackTask"] = task_entity From 6f2132b0aa94d412ef37a0e28b6abaca217d2fdf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:26:10 +0200 Subject: [PATCH 180/947] added function _asset_version_status for getting statuses --- .../plugins/ftrack/publish/integrate_ftrack_api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index cd94b2a150..59740529bb 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -54,8 +54,20 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): self.log.debug(query) return query - def process(self, instance): + def _asset_version_status(self, instance, session): + status_name = instance.context.data.get("ftrackStatus") + if not status_name: + return None + project_entity = instance.context.data.get("ftrackProject") + project_schema = project_entity["project_schema"] + asset_version_statuses = project_schema.get_statuses("AssetVersion") + asset_version_statuses_by_low_name = { + status["name"].lower(): status for status in asset_version_statuses + } + return asset_version_statuses_by_low_name.get(status_name.lower()) + + def process(self, instance): session = instance.context.data["ftrackSession"] if instance.data.get("ftrackTask"): task = instance.data["ftrackTask"] From 94be335e91f3a7c154db5c4860d35d7c37cb1508 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:27:37 +0200 Subject: [PATCH 181/947] change staus of asset version if is set --- .../ftrack/publish/integrate_ftrack_api.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index 59740529bb..e5f62a93ea 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -90,9 +90,11 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): info_msg += ", metadata: {metadata}." used_asset_versions = [] + + asset_version_status = self._asset_version_status(instance, session) + # Iterate over components and publish for data in instance.data.get("ftrackComponentsList", []): - # AssetType # Get existing entity. assettype_data = {"short": "upload"} @@ -106,9 +108,9 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): # Create a new entity if none exits. if not assettype_entity: assettype_entity = session.create("AssetType", assettype_data) - self.log.debug( - "Created new AssetType with data: ".format(assettype_data) - ) + self.log.debug("Created new AssetType with data: {}".format( + assettype_data + )) # Asset # Get existing entity. @@ -215,6 +217,17 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): assetversion_entity["id"], str(asset_version_comment) )) + if asset_version_status: + assetversion_entity["status"] = asset_version_status + try: + session.commit() + except Exception: + session.rollback() + status_name = instance.context.data["ftrackStatus"] + self.log.warning(( + "Couldn't set status \"{0}\" to AssetVersion \"{1}\"." + ).format(status_name, assetversion_entity["id"])) + # Adding Custom Attributes for attr, val in assetversion_cust_attrs.items(): if attr in assetversion_entity["custom_attributes"]: From d14975d9b1899594d0e7553974f385f80524f010 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:46:25 +0200 Subject: [PATCH 182/947] change status on task not on asset version --- .../ftrack/publish/integrate_ftrack_api.py | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index e5f62a93ea..0c4c6d49b5 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -54,18 +54,50 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): self.log.debug(query) return query - def _asset_version_status(self, instance, session): + def _set_task_status(self, instance, task_entity, session): + project_entity = instance.context.data.get("ftrackProject") + if not project_entity: + self.log.info("Task status won't be set, project is not known.") + return + + if not task_entity: + self.log.info("Task status won't be set, task is not known.") + return + status_name = instance.context.data.get("ftrackStatus") if not status_name: - return None + self.log.info("Ftrack status name is not set.") + return + + self.log.debug( + "Ftrack status name will be (maybe) set to \"{}\"".format( + status_name + ) + ) - project_entity = instance.context.data.get("ftrackProject") project_schema = project_entity["project_schema"] - asset_version_statuses = project_schema.get_statuses("AssetVersion") - asset_version_statuses_by_low_name = { - status["name"].lower(): status for status in asset_version_statuses + task_statuses = project_schema.get_statuses( + "Task", task_entity["type_id"] + ) + task_statuses_by_low_name = { + status["name"].lower(): status for status in task_statuses } - return asset_version_statuses_by_low_name.get(status_name.lower()) + status = task_statuses_by_low_name.get(status_name.lower()) + if not status: + self.log.warning(( + "Task status \"{}\" won't be set," + " status is now allowed on task type \"{}\"." + ).format(status_name, task_entity["type"]["name"])) + return + + self.log.info("Setting task status to \"{}\"".format(status_name)) + task_entity["status"] = status + try: + session.commit() + except Exception: + tp, value, tb = sys.exc_info() + session.rollback() + six.reraise(tp, value, tb) def process(self, instance): session = instance.context.data["ftrackSession"] @@ -91,7 +123,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): used_asset_versions = [] - asset_version_status = self._asset_version_status(instance, session) + self._set_task_status(instance, task, session) # Iterate over components and publish for data in instance.data.get("ftrackComponentsList", []): @@ -217,17 +249,6 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): assetversion_entity["id"], str(asset_version_comment) )) - if asset_version_status: - assetversion_entity["status"] = asset_version_status - try: - session.commit() - except Exception: - session.rollback() - status_name = instance.context.data["ftrackStatus"] - self.log.warning(( - "Couldn't set status \"{0}\" to AssetVersion \"{1}\"." - ).format(status_name, assetversion_entity["id"])) - # Adding Custom Attributes for attr, val in assetversion_cust_attrs.items(): if attr in assetversion_entity["custom_attributes"]: From eec1d35e3f60440e3386bdcbbb66f7aa6a468ba0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 13:04:58 +0200 Subject: [PATCH 183/947] skip scene for changing version to taks statuses --- pype/modules/ftrack/events/event_version_to_task_statuses.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/modules/ftrack/events/event_version_to_task_statuses.py b/pype/modules/ftrack/events/event_version_to_task_statuses.py index 3ff986f9c6..fdb48cbc37 100644 --- a/pype/modules/ftrack/events/event_version_to_task_statuses.py +++ b/pype/modules/ftrack/events/event_version_to_task_statuses.py @@ -84,6 +84,9 @@ class VersionToTaskStatus(BaseEvent): if not task: continue + if version["asset"]["type"]["short"].lower() == "scene": + continue + project_schema = task["project"]["project_schema"] # Get all available statuses for Task statuses = project_schema.get_statuses("Task", task["type_id"]) From c05f3060ba1f402f41a2dce573d11da219dbf818 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 27 Jul 2020 13:14:09 +0200 Subject: [PATCH 184/947] version bump --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index 1c622223ba..7f6646a762 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.10.0" +__version__ = "2.11.0" From 86119ea823c7436461e6aa4ca3681ea3febcd9d3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 27 Jul 2020 13:37:16 +0200 Subject: [PATCH 185/947] support for updating renderSetup settings --- pype/plugins/maya/load/load_rendersetup.py | 63 +++++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/pype/plugins/maya/load/load_rendersetup.py b/pype/plugins/maya/load/load_rendersetup.py index b38e2988b1..fae79548a5 100644 --- a/pype/plugins/maya/load/load_rendersetup.py +++ b/pype/plugins/maya/load/load_rendersetup.py @@ -1,14 +1,23 @@ -from avalon import api -import maya.app.renderSetup.model.renderSetup as renderSetup -from avalon.maya import lib -from maya import cmds +# -*- coding: utf-8 -*- +"""Load and update RenderSetup settings. + +Working with RenderSetup setting is Maya is done utilizing json files. +When this json is loaded, it will overwrite all settings on RenderSetup +instance. +""" + import json +from avalon import api +from avalon.maya import lib +from pype.hosts.maya import lib as pypelib + +from maya import cmds +import maya.app.renderSetup.model.renderSetup as renderSetup + class RenderSetupLoader(api.Loader): - """ - This will load json preset for RenderSetup, overwriting current one. - """ + """Load json preset for RenderSetup overwriting current one.""" families = ["rendersetup"] representations = ["json"] @@ -19,7 +28,7 @@ class RenderSetupLoader(api.Loader): color = "orange" def load(self, context, name, namespace, data): - + """Load RenderSetup settings.""" from avalon.maya.pipeline import containerise # from pype.hosts.maya.lib import namespaced @@ -48,3 +57,41 @@ class RenderSetupLoader(api.Loader): nodes=nodes, context=context, loader=self.__class__.__name__) + + def remove(self, container): + """Remove RenderSetup settings instance.""" + from maya import cmds + + namespace = container["namespace"] + container_name = container["objectName"] + + self.log.info("Removing '%s' from Maya.." % container["name"]) + + container_content = cmds.sets(container_name, query=True) + nodes = cmds.ls(container_content, long=True) + + nodes.append(container_name) + + try: + cmds.delete(nodes) + except ValueError: + # Already implicitly deleted by Maya upon removing reference + pass + + cmds.namespace(removeNamespace=namespace, deleteNamespaceContent=True) + + def update(self, container, representation): + """Update RenderSetup setting by overwriting existing settings.""" + pypelib.show_message( + "Render setup update", + "Render setup setting will be overwritten by new version. All " + "setting specified by user not included in loaded version " + "will be lost.") + path = api.get_representation_path(representation) + with open(path, "r") as file: + renderSetup.instance().decode( + json.load(file), renderSetup.DECODE_AND_OVERWRITE, None) + + def switch(self, container, representation): + """Switch representations.""" + self.update(container, representation) From 9ed0639789eccd23f3e41fdb917f4adb118f60dd Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 27 Jul 2020 17:10:02 +0200 Subject: [PATCH 186/947] add mb to maya loaders --- pype/plugins/maya/load/actions.py | 2 +- pype/plugins/maya/load/load_reference.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/load/actions.py b/pype/plugins/maya/load/actions.py index 77d18b0ee3..d4525511f4 100644 --- a/pype/plugins/maya/load/actions.py +++ b/pype/plugins/maya/load/actions.py @@ -87,7 +87,7 @@ class ImportMayaLoader(api.Loader): so you could also use it as a new base. """ - representations = ["ma"] + representations = ["ma", "mb"] families = ["*"] label = "Import" diff --git a/pype/plugins/maya/load/load_reference.py b/pype/plugins/maya/load/load_reference.py index 5992980412..3a9b75a2f5 100644 --- a/pype/plugins/maya/load/load_reference.py +++ b/pype/plugins/maya/load/load_reference.py @@ -16,7 +16,7 @@ class ReferenceLoader(pype.hosts.maya.plugin.ReferenceLoader): "layout", "camera", "rig"] - representations = ["ma", "abc", "fbx"] + representations = ["ma", "abc", "fbx", "mb"] tool_names = ["loader"] label = "Reference" From 8ebbf14097557f22dd87f59a040bc1f353cd221d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 17:36:04 +0200 Subject: [PATCH 187/947] added child overriden attribute --- pype/tools/config_setting/style/style.css | 8 +++ pype/tools/config_setting/widgets/inputs.py | 70 +++++++++++++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css index db2d746a23..1371056c1a 100644 --- a/pype/tools/config_setting/style/style.css +++ b/pype/tools/config_setting/style/style.css @@ -88,6 +88,14 @@ QPushButton[btn-type="expand-toggle"] { border-color: #137cbd; } +#DictExpandWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"] { + border-color: #ff8c1a; +} + +#DictExpandWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"] { + border-color: #00b386; +} + #TextListSubWidget { border: 1px solid #455c6e; border-radius: 3px; diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 01a22bd702..9bed1207a8 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -19,8 +19,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._as_widget = values is AS_WIDGET self._parent = parent - print(10*"*", parent) - print(values) + self.is_modified = False self.is_group = False self.is_overriden = False @@ -72,6 +71,10 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_modified(self): return self.is_modified + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -157,6 +160,10 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_modified(self): return self.is_modified + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -266,6 +273,10 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_modified(self): return self.is_modified + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -365,6 +376,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_modified(self): return self.is_modified + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -461,6 +476,10 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_modified(self): return self.is_modified + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -718,6 +737,10 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_modified(self): return self.is_modified + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -866,8 +889,15 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._update_style() def _update_style(self): - if self.child_modified: + child_modified = self.child_modified + child_overriden = self.child_overriden + + if child_modified and child_overriden: + widget_state = "child-overriden-modified" + elif child_modified: widget_state = "child-modified" + elif child_overriden: + widget_state = "child-overriden" else: widget_state = "" @@ -894,6 +924,13 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): return True return False + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + def item_value(self): output = {} for input_field in self.input_fields: @@ -966,6 +1003,13 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): return True return False + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + def item_value(self): output = {} for input_field in self.input_fields: @@ -1035,6 +1079,13 @@ class DictFormWidget(QtWidgets.QWidget): return True return False + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + @property def is_overidable(self): return self._parent.is_overidable @@ -1349,13 +1400,24 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self._update_style() + @property + def child_overriden(self): + return self.is_overriden + @property def is_overidable(self): return self._parent.is_overidable def _update_style(self): - if self.child_modified: + child_modified = self.child_modified + child_overriden = self.child_overriden + + if child_modified and child_overriden: + widget_state = "child-overriden-modified" + elif child_modified: widget_state = "child-modified" + elif child_overriden: + widget_state = "child-overriden" else: widget_state = "" From 669631d71edc96c8adeb5ff32954389a4f5edfb7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 18:55:36 +0200 Subject: [PATCH 188/947] removed duplicated class --- pype/tools/config_setting/widgets/inputs.py | 50 --------------------- 1 file changed, 50 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 9bed1207a8..01c69c1c0b 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1112,56 +1112,6 @@ class DictFormWidget(QtWidgets.QWidget): return item -class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): - _btn_size = 20 - value_changed = QtCore.Signal() - - def __init__(self, parent): - super(TextListItem, self).__init__(parent) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(3) - - self.text_input = QtWidgets.QLineEdit() - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") - - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") - - layout.addWidget(self.text_input, 1) - layout.addWidget(self.add_btn, 0) - layout.addWidget(self.remove_btn, 0) - - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) - - self.text_input.textChanged.connect(self._on_value_change) - - self.is_single = False - - def _on_value_change(self): - self.value_changed.emit() - - def row(self): - return self.parent().input_fields.index(self) - - def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) - - def on_remove_clicked(self): - if self.is_single: - self.text_input.setText("") - else: - self.parent().remove_row(self) - - def config_value(self): - return self.text_input.text() - - class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 value_changed = QtCore.Signal() From a8fb14a1bca842e45c81eb2d60b16f1a68f1505b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 18:55:53 +0200 Subject: [PATCH 189/947] added is_group feature --- pype/tools/config_setting/widgets/inputs.py | 160 ++++++++++++++++++-- 1 file changed, 145 insertions(+), 15 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 01c69c1c0b..485eda6d35 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -11,6 +11,14 @@ from .widgets import ( from .lib import NOT_SET, AS_WIDGET +class SchemeGroupHierarchyBug(Exception): + def __init__(self, msg=None): + if not msg: + # TODO better message + msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" + super(SchemeGroupHierarchyBug, self).__init(msg) + + class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal() @@ -20,8 +28,19 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._as_widget = values is AS_WIDGET self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(BooleanWidget, self).__init__(parent) @@ -122,8 +141,19 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(IntegerWidget, self).__init__(parent) @@ -225,8 +255,19 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(FloatWidget, self).__init__(parent) @@ -338,8 +379,19 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(TextSingleLineWidget, self).__init__(parent) @@ -440,8 +492,19 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(TextMultiLineWidget, self).__init__(parent) @@ -699,8 +762,16 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + self.is_modified = False - self.is_group = False + self.is_group = is_group self.is_overriden = False super(TextListWidget, self).__init__(parent) @@ -747,7 +818,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_group and self.is_overidable: + self.is_overriden = True self._update_style() @@ -798,9 +870,19 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): )) self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False - self.is_overriden = False - self.is_group = input_data.get("is_group", False) + self._is_overriden = False + self.is_group = is_group super(DictExpandWidget, self).__init__(parent) self.setObjectName("DictExpandWidget") @@ -850,7 +932,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) - self._is_overriden = False self.input_fields = [] self.key = input_data["key"] @@ -881,8 +962,15 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): super(DictExpandWidget, self).resizeEvent(event) self.content_widget.updateGeometry() + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + + def _on_value_change(self, value=None): - self.is_overriden = True + if self.is_group: + self._is_overriden = True self.value_changed.emit() @@ -966,9 +1054,19 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False self.is_overriden = False - self.is_group = input_data.get("is_group", False) + self.is_group = is_group super(DictInvisible, self).__init__(parent) self.setObjectName("DictInvisible") @@ -979,7 +1077,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self._is_overriden = False self.input_fields = [] if "key" not in input_data: @@ -1047,6 +1144,12 @@ class DictFormWidget(QtWidgets.QWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False self.is_overriden = False self.is_group = False @@ -1168,6 +1271,14 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): self._update_style() self.value_changed.emit() + @property + def is_group(self): + return self._parent.is_group + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + @property def is_overidable(self): return self._parent.is_overidable @@ -1253,6 +1364,14 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_overidable(self): return self._parent.is_overidable + @property + def is_group(self): + return self._parent.is_group + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + def _on_value_change(self): self.value_changed.emit() @@ -1324,10 +1443,20 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False self.child_modified = False self.is_overriden = False - self.is_group = input_data.get("is_group", False) + self.is_group = is_group super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") @@ -1346,7 +1475,8 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.child_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_group: + self.is_overriden = True self._update_style() From e7e3526c72b5ca06f716bb195e2dfc99b3be0ee0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 18:56:04 +0200 Subject: [PATCH 190/947] added is_group feature --- pype/tools/config_setting/widgets/base.py | 6 +- pype/tools/config_setting/widgets/inputs.py | 160 ++++++++++++++++++-- 2 files changed, 150 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index b68ec4126a..222cf2ba41 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -50,6 +50,8 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): "config_gui_schema" ) is_overidable = False + is_group = False + any_parent_is_group = False def __init__(self, parent=None): super(StudioWidget, self).__init__(parent) @@ -124,7 +126,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): while not process_queue.empty(): path, key, metadata, values = process_queue.get() new_path = os.path.join(path, key) - # TODO this should not be + # TODO this should not be if key in metadata: key_metadata = metadata[key] @@ -256,6 +258,8 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): "config_gui_schema" ) is_overidable = True + is_group = False + any_parent_is_group = False def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 01c69c1c0b..485eda6d35 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -11,6 +11,14 @@ from .widgets import ( from .lib import NOT_SET, AS_WIDGET +class SchemeGroupHierarchyBug(Exception): + def __init__(self, msg=None): + if not msg: + # TODO better message + msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" + super(SchemeGroupHierarchyBug, self).__init(msg) + + class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal() @@ -20,8 +28,19 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._as_widget = values is AS_WIDGET self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(BooleanWidget, self).__init__(parent) @@ -122,8 +141,19 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(IntegerWidget, self).__init__(parent) @@ -225,8 +255,19 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(FloatWidget, self).__init__(parent) @@ -338,8 +379,19 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(TextSingleLineWidget, self).__init__(parent) @@ -440,8 +492,19 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group self.is_modified = False - self.is_group = False self.is_overriden = False super(TextMultiLineWidget, self).__init__(parent) @@ -699,8 +762,16 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + self.is_modified = False - self.is_group = False + self.is_group = is_group self.is_overriden = False super(TextListWidget, self).__init__(parent) @@ -747,7 +818,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_group and self.is_overidable: + self.is_overriden = True self._update_style() @@ -798,9 +870,19 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): )) self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False - self.is_overriden = False - self.is_group = input_data.get("is_group", False) + self._is_overriden = False + self.is_group = is_group super(DictExpandWidget, self).__init__(parent) self.setObjectName("DictExpandWidget") @@ -850,7 +932,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) - self._is_overriden = False self.input_fields = [] self.key = input_data["key"] @@ -881,8 +962,15 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): super(DictExpandWidget, self).resizeEvent(event) self.content_widget.updateGeometry() + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + + def _on_value_change(self, value=None): - self.is_overriden = True + if self.is_group: + self._is_overriden = True self.value_changed.emit() @@ -966,9 +1054,19 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False self.is_overriden = False - self.is_group = input_data.get("is_group", False) + self.is_group = is_group super(DictInvisible, self).__init__(parent) self.setObjectName("DictInvisible") @@ -979,7 +1077,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self._is_overriden = False self.input_fields = [] if "key" not in input_data: @@ -1047,6 +1144,12 @@ class DictFormWidget(QtWidgets.QWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False self.is_overriden = False self.is_group = False @@ -1168,6 +1271,14 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): self._update_style() self.value_changed.emit() + @property + def is_group(self): + return self._parent.is_group + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + @property def is_overidable(self): return self._parent.is_overidable @@ -1253,6 +1364,14 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_overidable(self): return self._parent.is_overidable + @property + def is_group(self): + return self._parent.is_group + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + def _on_value_change(self): self.value_changed.emit() @@ -1324,10 +1443,20 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): ): self._parent = parent + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + self.is_modified = False self.child_modified = False self.is_overriden = False - self.is_group = input_data.get("is_group", False) + self.is_group = is_group super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") @@ -1346,7 +1475,8 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.child_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_group: + self.is_overriden = True self._update_style() From 9ef5bad10ea76dfe8beccd0925403715658c779b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 18:57:49 +0200 Subject: [PATCH 191/947] added first testing groups --- .../config_gui_schema/ftrack_projects_gui_schema.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json index febf84eb4a..2e930acca5 100644 --- a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json @@ -7,6 +7,7 @@ "key": "status_update", "type": "dict-expanding", "label": "Status updates", + "is_group": true, "children": [ { "key": "Ready", @@ -18,6 +19,7 @@ "key": "status_version_to_task", "type": "dict-expanding", "label": "Version status to Task status", + "is_group": true, "children": [ { "key": "in progress", From 9ec21131f3e258aea80cb762fc03a56eab0e627b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 28 Jul 2020 10:44:15 +0200 Subject: [PATCH 192/947] just fast commit --- pype/tools/config_setting/widgets/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 485eda6d35..c094dcd9db 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -966,7 +966,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_overriden(self): if self._is_overriden: return self._is_overriden - + return self._parent.is_overriden def _on_value_change(self, value=None): if self.is_group: From 0035dc009a57ba10992ff553272bb8584caaf91c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 28 Jul 2020 11:13:27 +0200 Subject: [PATCH 193/947] ensure that resolution is divisible by 2 --- pype/plugins/photoshop/publish/extract_review.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index d784dc0998..8aebd1ea87 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -78,6 +78,7 @@ class ExtractReview(pype.api.Extractor): args = [ ffmpeg_path, "-y", "-i", os.path.join(staging_dir, output_image), + "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-vframes", "1", mov_path ] From a87022b47fa7bf64a2c28096ac221ad494d31c03 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 28 Jul 2020 12:33:38 +0200 Subject: [PATCH 194/947] make sure we skip frames in proxy loader --- pype/plugins/maya/load/load_ass.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/plugins/maya/load/load_ass.py b/pype/plugins/maya/load/load_ass.py index 210b1fde1e..ffe70c39e8 100644 --- a/pype/plugins/maya/load/load_ass.py +++ b/pype/plugins/maya/load/load_ass.py @@ -98,15 +98,19 @@ class AssProxyLoader(pype.hosts.maya.plugin.ReferenceLoader): node = container["objectName"] + representation["context"].pop("frame", None) path = api.get_representation_path(representation) + print(path) # path = self.fname + print(self.fname) proxyPath = os.path.splitext(path)[0] + ".ma" + print(proxyPath) # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) reference_node = self._get_reference_node(members) - assert os.path.exists(path), "%s does not exist." % proxyPath + assert os.path.exists(proxyPath), "%s does not exist." % proxyPath try: content = cmds.file(proxyPath, From 7ce31ba1ecf45a5a03b5c4d3e3d19c1b780c9e4d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 28 Jul 2020 13:14:20 +0200 Subject: [PATCH 195/947] small code fixes --- pype/plugins/maya/load/load_rendersetup.py | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/pype/plugins/maya/load/load_rendersetup.py b/pype/plugins/maya/load/load_rendersetup.py index fae79548a5..45a314a9d1 100644 --- a/pype/plugins/maya/load/load_rendersetup.py +++ b/pype/plugins/maya/load/load_rendersetup.py @@ -7,6 +7,8 @@ instance. """ import json +import six +import sys from avalon import api from avalon.maya import lib @@ -38,7 +40,7 @@ class RenderSetupLoader(api.Loader): prefix="_" if asset[0].isdigit() else "", suffix="_", ) - + self.log.info(">>> loading json [ {} ]".format(self.fname)) with open(self.fname, "r") as file: renderSetup.instance().decode( json.load(file), renderSetup.DECODE_AND_OVERWRITE, None) @@ -51,6 +53,7 @@ class RenderSetupLoader(api.Loader): if not nodes: return + self.log.info(">>> containerising [ {} ]".format(name)) return containerise( name=name, namespace=namespace, @@ -62,7 +65,6 @@ class RenderSetupLoader(api.Loader): """Remove RenderSetup settings instance.""" from maya import cmds - namespace = container["namespace"] container_name = container["objectName"] self.log.info("Removing '%s' from Maya.." % container["name"]) @@ -78,8 +80,6 @@ class RenderSetupLoader(api.Loader): # Already implicitly deleted by Maya upon removing reference pass - cmds.namespace(removeNamespace=namespace, deleteNamespaceContent=True) - def update(self, container, representation): """Update RenderSetup setting by overwriting existing settings.""" pypelib.show_message( @@ -89,8 +89,19 @@ class RenderSetupLoader(api.Loader): "will be lost.") path = api.get_representation_path(representation) with open(path, "r") as file: - renderSetup.instance().decode( - json.load(file), renderSetup.DECODE_AND_OVERWRITE, None) + try: + renderSetup.instance().decode( + json.load(file), renderSetup.DECODE_AND_OVERWRITE, None) + except Exception: + self.log.error("There were errors during loading") + six.reraise(*sys.exc_info()) + + # Update metadata + node = container["objectName"] + cmds.setAttr("{}.representation".format(node), + str(representation["_id"]), + type="string") + self.log.info("... updated") def switch(self, container, representation): """Switch representations.""" From 9527904cff10c930f6d8238dc85a71a1b758a856 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Jul 2020 14:26:25 +0200 Subject: [PATCH 196/947] fix(celaction): version up method taken fro pype.lib --- .../celaction/publish/integrate_version_up.py | 52 +------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/pype/plugins/celaction/publish/integrate_version_up.py b/pype/plugins/celaction/publish/integrate_version_up.py index 1822ceabcb..e15c5d5bf6 100644 --- a/pype/plugins/celaction/publish/integrate_version_up.py +++ b/pype/plugins/celaction/publish/integrate_version_up.py @@ -1,5 +1,5 @@ import shutil -import re +import pype import pyblish.api @@ -12,57 +12,9 @@ class VersionUpScene(pyblish.api.ContextPlugin): def process(self, context): current_file = context.data.get('currentFile') - v_up = get_version_up(current_file) + v_up = pype.lib.version_up(current_file) self.log.debug('Current file is: {}'.format(current_file)) self.log.debug('Version up: {}'.format(v_up)) shutil.copy2(current_file, v_up) self.log.info('Scene saved into new version: {}'.format(v_up)) - - -def version_get(string, prefix, suffix=None): - """Extract version information from filenames used by DD (and Weta, apparently) - These are _v# or /v# or .v# where v is a prefix string, in our case - we use "v" for render version and "c" for camera track version. - See the version.py and camera.py plugins for usage.""" - - if string is None: - raise ValueError("Empty version string - no match") - - regex = r"[/_.]{}\d+".format(prefix) - matches = re.findall(regex, string, re.IGNORECASE) - if not len(matches): - msg = f"No `_{prefix}#` found in `{string}`" - raise ValueError(msg) - return (matches[-1:][0][1], re.search(r"\d+", matches[-1:][0]).group()) - - -def version_set(string, prefix, oldintval, newintval): - """Changes version information from filenames used by DD (and Weta, apparently) - These are _v# or /v# or .v# where v is a prefix string, in our case - we use "v" for render version and "c" for camera track version. - See the version.py and camera.py plugins for usage.""" - - regex = r"[/_.]{}\d+".format(prefix) - matches = re.findall(regex, string, re.IGNORECASE) - if not len(matches): - return "" - - # Filter to retain only version strings with matching numbers - matches = filter(lambda s: int(s[2:]) == oldintval, matches) - - # Replace all version strings with matching numbers - for match in matches: - # use expression instead of expr so 0 prefix does not make octal - fmt = "%%(#)0%dd" % (len(match) - 2) - newfullvalue = match[0] + prefix + str(fmt % {"#": newintval}) - string = re.sub(match, newfullvalue, string) - return string - - -def get_version_up(path): - """ Returns the next version of the path """ - - (prefix, v) = version_get(path, 'v') - v = int(v) - return version_set(path, prefix, v, v + 1) From 2a4d075ae5095d077068d47534ae2aceee63d794 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 28 Jul 2020 15:30:22 +0200 Subject: [PATCH 197/947] plugins extract review and extract image in photoshop does not use SaveAs to folder without specifying the filename --- pype/plugins/photoshop/publish/extract_image.py | 16 ++++++++-------- pype/plugins/photoshop/publish/extract_review.py | 11 +++++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py index da3197c7da..1bb13bce6b 100644 --- a/pype/plugins/photoshop/publish/extract_image.py +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -36,16 +36,16 @@ class ExtractImage(pype.api.Extractor): "png": photoshop.com_objects.PNGSaveOptions(), "jpg": photoshop.com_objects.JPEGSaveOptions() } - + file_basename = os.path.splitext( + photoshop.app().ActiveDocument.Name + )[0] for extension, save_option in save_options.items(): + _filename = "{}.{}".format(file_basename, extension) + files[extension] = _filename + + full_filename = os.path.join(staging_dir, _filename) photoshop.app().ActiveDocument.SaveAs( - staging_dir, save_option, True - ) - files[extension] = "{} copy.{}".format( - os.path.splitext( - photoshop.app().ActiveDocument.Name - )[0], - extension + full_filename, save_option, True ) representations = [] diff --git a/pype/plugins/photoshop/publish/extract_review.py b/pype/plugins/photoshop/publish/extract_review.py index 8aebd1ea87..078ee53899 100644 --- a/pype/plugins/photoshop/publish/extract_review.py +++ b/pype/plugins/photoshop/publish/extract_review.py @@ -24,9 +24,10 @@ class ExtractReview(pype.api.Extractor): layers.append(image_instance[0]) # Perform extraction - output_image = "{} copy.jpg".format( + output_image = "{}.jpg".format( os.path.splitext(photoshop.app().ActiveDocument.Name)[0] ) + output_image_path = os.path.join(staging_dir, output_image) with photoshop.maintained_visibility(): # Hide all other layers. extract_ids = [ @@ -39,7 +40,9 @@ class ExtractReview(pype.api.Extractor): layer.Visible = False photoshop.app().ActiveDocument.SaveAs( - staging_dir, photoshop.com_objects.JPEGSaveOptions(), True + output_image_path, + photoshop.com_objects.JPEGSaveOptions(), + True ) ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") @@ -56,7 +59,7 @@ class ExtractReview(pype.api.Extractor): thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") args = [ ffmpeg_path, "-y", - "-i", os.path.join(staging_dir, output_image), + "-i", output_image_path, "-vf", "scale=300:-1", "-vframes", "1", thumbnail_path @@ -77,7 +80,7 @@ class ExtractReview(pype.api.Extractor): mov_path = os.path.join(staging_dir, "review.mov") args = [ ffmpeg_path, "-y", - "-i", os.path.join(staging_dir, output_image), + "-i", output_image_path, "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-vframes", "1", mov_path From 2d553e5cc405a2fc42fc3858b09884760a476e3f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Jul 2020 15:39:18 +0200 Subject: [PATCH 198/947] feat(celactoin, global): metadata json folder fixes on path --- .../celaction/publish/collect_render_path.py | 13 ++++++++----- pype/plugins/global/publish/submit_publish_job.py | 10 +++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index a3918a52b6..9cbb0e4880 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -12,7 +12,7 @@ class CollectRenderPath(pyblish.api.InstancePlugin): # Presets anatomy_render_key = None - anatomy_publish_render_key = None + publish_render_metadata = None def process(self, instance): anatomy = instance.context.data["anatomy"] @@ -28,7 +28,7 @@ class CollectRenderPath(pyblish.api.InstancePlugin): # get anatomy rendering keys anatomy_render_key = self.anatomy_render_key or "render" - anatomy_publish_render_key = self.anatomy_publish_render_key or "render" + publish_render_metadata = self.publish_render_metadata or "render" # get folder and path for rendering images from celaction render_dir = anatomy_filled[anatomy_render_key]["folder"] @@ -46,8 +46,11 @@ class CollectRenderPath(pyblish.api.InstancePlugin): instance.data["path"] = render_path # get anatomy for published renders folder path - if anatomy_filled.get(anatomy_publish_render_key): - instance.data["publishRenderFolder"] = anatomy_filled[ - anatomy_publish_render_key]["folder"] + if anatomy_filled.get(publish_render_metadata): + instance.data["publishRenderMetadataFolder"] = anatomy_filled[ + publish_render_metadata]["folder"] + self.log.info("Metadata render path: `{}`".format( + instance.data["publishRenderMetadataFolder"] + )) self.log.info(f"Render output path set to: `{render_path}`") diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 9f89466c31..e7a426097f 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -206,7 +206,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): def _create_metadata_path(self, instance): ins_data = instance.data # Ensure output dir exists - output_dir = ins_data.get("publishRenderFolder", ins_data["outputDir"]) + output_dir = ins_data.get( + "publishRenderMetadataFolder", ins_data["outputDir"]) try: if not os.path.isdir(output_dir): @@ -715,6 +716,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if item in instance.data.get("families", []): instance_skeleton_data["families"] += [item] + if "render.farm" in instance.data["families"]: + instance_skeleton_data.update({ + "family": "render2d", + "families": ["render"] + [f for f in instance.data["families"] + if "render.farm" not in f] + }) + # transfer specific properties from original instance based on # mapping dictionary `instance_transfer` for key, values in self.instance_transfer.items(): From 7df271afb3e7dd32516eff24e6fed22bf4d3ef24 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Jul 2020 15:57:56 +0200 Subject: [PATCH 199/947] fix(global): unneeded code --- pype/plugins/global/publish/submit_publish_job.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index e7a426097f..1ea31d4a0b 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -716,13 +716,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if item in instance.data.get("families", []): instance_skeleton_data["families"] += [item] - if "render.farm" in instance.data["families"]: - instance_skeleton_data.update({ - "family": "render2d", - "families": ["render"] + [f for f in instance.data["families"] - if "render.farm" not in f] - }) - # transfer specific properties from original instance based on # mapping dictionary `instance_transfer` for key, values in self.instance_transfer.items(): From d234181f93d17de70706b2cd26b1c5fe0ec4a8e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 28 Jul 2020 17:08:56 +0200 Subject: [PATCH 200/947] added second item for testing --- .../config_gui_schema/ftrack_projects_gui_schema.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json index 2e930acca5..4a35fc9b61 100644 --- a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json @@ -13,6 +13,10 @@ "key": "Ready", "type": "text-singleline", "label": "Ready" + }, { + "key": "Ready2", + "type": "text-singleline", + "label": "Ready2" } ] }, { From 57a040fadbfbba1e672c3abb7ab044ac0be908aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 28 Jul 2020 17:28:14 +0200 Subject: [PATCH 201/947] better overridable control --- pype/tools/config_setting/widgets/base.py | 4 +- pype/tools/config_setting/widgets/inputs.py | 195 +++++++++++++------- 2 files changed, 128 insertions(+), 71 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 222cf2ba41..ddeae365d8 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -50,6 +50,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): "config_gui_schema" ) is_overidable = False + is_overriden = False is_group = False any_parent_is_group = False @@ -257,13 +258,14 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): os.path.dirname(os.path.dirname(__file__)), "config_gui_schema" ) - is_overidable = True + is_overriden = False is_group = False any_parent_is_group = False def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) + self.is_overidable = True self.input_fields = [] scroll_widget = QtWidgets.QScrollArea(self) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index c094dcd9db..2b1b5b3af6 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -41,7 +41,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False - self.is_overriden = False + self._is_overriden = False super(BooleanWidget, self).__init__(parent) @@ -92,26 +92,33 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_overidable: + self._is_overriden = True self._update_style() self.value_changed.emit() def _update_style(self): - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" + is_overriden = self.is_overriden + if is_overriden and self.is_modified: + state = "overriden-modified" + elif is_overriden: + state = "overriden" elif self.is_modified: state = "modified" else: @@ -154,7 +161,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False - self.is_overriden = False + self._is_overriden = False super(IntegerWidget, self).__init__(parent) @@ -192,12 +199,18 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def set_value(self, value, origin_value=False): self.int_input.setValue(value) if origin_value: @@ -212,18 +225,19 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_overidable: + self._is_overriden = True self._update_style() self.value_changed.emit() def _update_style(self): - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" + is_overriden = self.is_overriden + if is_overriden and self.is_modified: + state = "overriden-modified" + elif is_overriden: + state = "overriden" elif self.is_modified: state = "modified" else: @@ -268,7 +282,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False - self.is_overriden = False + self._is_overriden = False super(FloatWidget, self).__init__(parent) @@ -316,12 +330,18 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def set_value(self, value, origin_value=False): self.float_input.setValue(value) if origin_value: @@ -336,18 +356,19 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_overidable: + self._is_overriden = True self._update_style() self.value_changed.emit() def _update_style(self): - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" + is_overriden = self.is_overriden + if is_overriden and self.is_modified: + state = "overriden-modified" + elif is_overriden: + state = "overriden" elif self.is_modified: state = "modified" else: @@ -392,7 +413,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False - self.is_overriden = False + self._is_overriden = False super(TextSingleLineWidget, self).__init__(parent) @@ -430,12 +451,18 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def set_value(self, value, origin_value=False): self.text_input.setText(value) if origin_value: @@ -450,18 +477,19 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_overidable: + self._is_overriden = True self._update_style() self.value_changed.emit() def _update_style(self): - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" + is_overriden = self.is_overriden + if is_overriden and self.is_modified: + state = "overriden-modified" + elif is_overriden: + state = "overriden" elif self.is_modified: state = "modified" else: @@ -505,7 +533,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False - self.is_overriden = False + self._is_overriden = False super(TextMultiLineWidget, self).__init__(parent) @@ -541,12 +569,18 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def set_value(self, value, origin_value=False): self.text_input.setPlainText(value) if origin_value: @@ -561,18 +595,19 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - self.is_overriden = True + if self.is_overidable: + self._is_overriden = True self._update_style() self.value_changed.emit() def _update_style(self): - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" + is_overriden = self.is_overriden + if is_overriden and self.is_modified: + state = "overriden-modified" + elif is_overriden: + state = "overriden" elif self.is_modified: state = "modified" else: @@ -770,9 +805,12 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() + if not any_parent_is_group and not is_group: + is_group = True + self.is_modified = False self.is_group = is_group - self.is_overriden = False + self._is_overriden = False super(TextListWidget, self).__init__(parent) self.setObjectName("TextListWidget") @@ -810,16 +848,22 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def _on_value_change(self, value=None): self.is_modified = self.item_value() != self.origin_value - if self.is_group and self.is_overidable: - self.is_overriden = True + if self.is_overidable: + self._is_overriden = True self._update_style() @@ -838,11 +882,11 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value([]) def _update_style(self): - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" + is_overriden = self.is_overriden + if is_overriden and self.is_modified: + state = "overriden-modified" + elif is_overriden: + state = "overriden" elif self.is_modified: state = "modified" else: @@ -969,7 +1013,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def _on_value_change(self, value=None): - if self.is_group: + if self.is_group and self.is_overidable: self._is_overriden = True self.value_changed.emit() @@ -992,13 +1036,10 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.setProperty("state", widget_state) self.style().polish(self) - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" - elif self.is_modified: - state = "modified" + if child_modified and self.is_overriden: + state = "overriden-modified" + elif self.is_overriden: + state = "overriden" else: state = "original" @@ -1065,7 +1106,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): self.any_parent_is_group = any_parent_is_group self.is_modified = False - self.is_overriden = False self.is_group = is_group super(DictInvisible, self).__init__(parent) @@ -1089,6 +1129,10 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + @property + def is_overriden(self): + return self._parent.is_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -1184,7 +1228,7 @@ class DictFormWidget(QtWidgets.QWidget): @property def child_overriden(self): - for input_field in self.input_fields: + for input_field in self.input_fields.values(): if input_field.child_overriden: return True return False @@ -1283,6 +1327,10 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + return self._parent.is_overriden + def is_key_modified(self): return self._key() != self.origin_key @@ -1364,6 +1412,10 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + return self._parent.is_overriden + @property def is_group(self): return self._parent.is_group @@ -1455,7 +1507,7 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.is_modified = False self.child_modified = False - self.is_overriden = False + self._is_overriden = False self.is_group = is_group super(ModifiableDict, self).__init__(input_data["label"], parent) @@ -1475,19 +1527,25 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def _on_value_change(self, value=None): self.child_modified = self.item_value() != self.origin_value - if self.is_group: - self.is_overriden = True + if self.is_group and self.is_overidable: + self._is_overriden = True self._update_style() @property def child_overriden(self): - return self.is_overriden + return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + def _update_style(self): child_modified = self.child_modified child_overriden = self.child_overriden @@ -1504,13 +1562,10 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.setProperty("state", widget_state) self.style().polish(self) - if self.is_overidable and self.is_overriden: - if self.is_modified: - state = "overriden-modified" - else: - state = "overriden" - elif self.is_modified: - state = "modified" + if child_modified and self.is_overriden: + state = "overriden-modified" + elif self.is_overriden: + state = "overriden" else: state = "original" From 616817df3e641ce56d6f1e33b631455c7fd768d7 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 28 Jul 2020 18:02:59 +0200 Subject: [PATCH 202/947] support for tile render job submitted from elsewhere --- .../global/publish/submit_publish_job.py | 71 +++++++++++++------ pype/plugins/maya/create/create_render.py | 8 ++- .../maya/publish/submit_maya_deadline.py | 17 ++--- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 9f89466c31..838b1717c2 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -252,7 +252,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "Plugin": "Python", "BatchName": job["Props"]["Batch"], "Name": job_name, - "JobDependency0": job["_id"], "UserName": job["Props"]["User"], "Comment": instance.context.data.get("comment", ""), @@ -275,6 +274,25 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Mandatory for Deadline, may be empty "AuxFiles": [], } + """ + In this part we will add file dependencies instead of job dependencies. + This way we don't need to take care of tile assembly job, getting its + id or name. We expect it to produce specific file with specific name + and we are just waiting for them. + """ + if instance.data.get("tileRendering"): + asset_index = 0 + for represenation in instance.data.get("representations", []): + if isinstance(represenation["files"], [list, tuple]): + for file in represenation["files"]: + dependency = os.path.join(output_dir, file) + payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 + asset_index += 1 + else: + dependency = os.path.join(output_dir, file) + payload["JobInfo"]["AssetDependency0"] = dependency + else: + payload["JobInfo"]["JobDependency0"] = job["_id"], # Transfer the environment from the original job to this dependent # job so they use the same environment @@ -613,25 +631,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if hasattr(instance, "_log"): data['_log'] = instance._log - render_job = data.pop("deadlineSubmissionJob", None) - submission_type = "deadline" - if not render_job: - # No deadline job. Try Muster: musterSubmissionJob - render_job = data.pop("musterSubmissionJob", None) - submission_type = "muster" - assert render_job, ( - "Can't continue without valid Deadline " - "or Muster submission prior to this " - "plug-in." - ) - - if submission_type == "deadline": - self.DEADLINE_REST_URL = os.environ.get( - "DEADLINE_REST_URL", "http://localhost:8082" - ) - assert self.DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" - - self._submit_deadline_post_job(instance, render_job) asset = data.get("asset") or api.Session["AVALON_ASSET"] subset = data.get("subset") @@ -846,6 +845,36 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): at.get("subset"), at.get("version"))) instances = new_instances + r''' SUBMiT PUBLiSH JOB 2 D34DLiN3 + ____ + ' ' .---. .---. .--. .---. .--..--..--..--. .---. + | | --= \ | . \/ _|/ \| . \ || || \ |/ _| + | JOB | --= / | | || __| .. | | | |;_ || \ || __| + | | |____./ \.__|._||_.|___./|_____|||__|\__|\.___| + ._____. + + ''' + + render_job = data.pop("deadlineSubmissionJob", None) + submission_type = "deadline" + if not render_job: + # No deadline job. Try Muster: musterSubmissionJob + render_job = data.pop("musterSubmissionJob", None) + submission_type = "muster" + assert render_job or instance.data.get("tileRendering") is False, ( + "Can't continue without valid Deadline " + "or Muster submission prior to this " + "plug-in." + ) + + if submission_type == "deadline": + self.DEADLINE_REST_URL = os.environ.get( + "DEADLINE_REST_URL", "http://localhost:8082" + ) + assert self.DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" + + self._submit_deadline_post_job(instance, render_job) + # publish job file publish_job = { "asset": asset, @@ -857,7 +886,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "version": context.data["version"], # this is workfile version "intent": context.data.get("intent"), "comment": context.data.get("comment"), - "job": render_job, + "job": render_job or None, "session": api.Session.copy(), "instances": instances } diff --git a/pype/plugins/maya/create/create_render.py b/pype/plugins/maya/create/create_render.py index 3b2048d8f0..9e5f9310ae 100644 --- a/pype/plugins/maya/create/create_render.py +++ b/pype/plugins/maya/create/create_render.py @@ -40,6 +40,9 @@ class CreateRender(avalon.maya.Creator): vrscene (bool): Submit as ``vrscene`` file for standalone V-Ray renderer. ass (bool): Submit as ``ass`` file for standalone Arnold renderer. + tileRendering (bool): Instance is set to tile rendering mode. We + won't submit actuall render, but we'll make publish job to wait + for Tile Assemly job done and then publish. See Also: https://pype.club/docs/artist_hosts_maya#creating-basic-render-setup @@ -181,6 +184,7 @@ class CreateRender(avalon.maya.Creator): self.data["machineList"] = "" self.data["useMayaBatch"] = False self.data["vrayScene"] = False + self.data["tileRendering"] = False # Disable for now as this feature is not working yet # self.data["assScene"] = False @@ -189,8 +193,8 @@ class CreateRender(avalon.maya.Creator): def _load_credentials(self): """Load Muster credentials. - Load Muster credentials from file and set ```MUSTER_USER``, - ```MUSTER_PASSWORD``, ``MUSTER_REST_URL`` is loaded from presets. + Load Muster credentials from file and set ``MUSTER_USER``, + ``MUSTER_PASSWORD``, ``MUSTER_REST_URL`` is loaded from presets. Raises: RuntimeError: If loaded credentials are invalid. diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index d81d43749c..3a6d12f623 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -392,18 +392,19 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.preflight_check(instance) # Submit job to farm ------------------------------------------------ - self.log.info("Submitting ...") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) + if not instance.data.get("tileRendering"): + self.log.info("Submitting ...") + self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(self._deadline_url) - response = self._requests_post(url, json=payload) - if not response.ok: - raise Exception(response.text) + # E.g. http://192.168.0.1:8082/api/jobs + url = "{}/api/jobs".format(self._deadline_url) + response = self._requests_post(url, json=payload) + if not response.ok: + raise Exception(response.text) + instance.data["deadlineSubmissionJob"] = response.json() # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) - instance.data["deadlineSubmissionJob"] = response.json() def _get_maya_payload(self, data): payload = copy.deepcopy(payload_skeleton) From f7323144e0073b2201aadaf02627390b28354db2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 28 Jul 2020 18:54:22 +0200 Subject: [PATCH 203/947] working overriden workflow --- pype/tools/config_setting/style/style.css | 6 - pype/tools/config_setting/widgets/base.py | 10 + pype/tools/config_setting/widgets/inputs.py | 313 ++++++++++---------- 3 files changed, 171 insertions(+), 158 deletions(-) diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css index 1371056c1a..aabffc3f84 100644 --- a/pype/tools/config_setting/style/style.css +++ b/pype/tools/config_setting/style/style.css @@ -19,8 +19,6 @@ QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus { border: 1px solid #ffffff; } -QLabel[state="original"] {} - QLabel[state="modified"] { color: #137cbd; } @@ -33,8 +31,6 @@ QLabel[state="overriden"] { color: #ff8c1a; } -QWidget[input-state="original"] {} - QWidget[input-state="modified"] { border-color: #137cbd; } @@ -61,8 +57,6 @@ QPushButton[btn-type="expand-toggle"] { background: transparent; } -#DictKey[state="original"] {} - #DictKey[state="modified"] { border-color: #137cbd; } diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index ddeae365d8..e01f14aa70 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -12,6 +12,8 @@ class TypeToKlass: class PypeConfigurationWidget: + default_state = "" + def config_value(self): raise NotImplementedError( "Method `config_value` is not implemented for `{}`.".format( @@ -38,6 +40,14 @@ class PypeConfigurationWidget: value = value[key] return value + def style_state(self, is_overriden, is_modified): + items = [] + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or self.default_state + def add_children_gui(self, child_configuration, values): raise NotImplementedError(( "Method `add_children_gui` is not implemented for `{}`." diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 2b1b5b3af6..e5b929b7ca 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -20,7 +20,7 @@ class SchemeGroupHierarchyBug(Exception): class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -43,6 +43,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_modified = False self._is_overriden = False + self._state = None + super(BooleanWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -104,25 +106,23 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() - self.value_changed.emit() + self.value_changed.emit(self) - def _update_style(self): - is_overriden = self.is_overriden - if is_overriden and self.is_modified: - state = "overriden-modified" - elif is_overriden: - state = "overriden" - elif self.is_modified: - state = "modified" - else: - state = "original" + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return if self._as_widget: property_name = "input-state" @@ -131,6 +131,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.label_widget.setProperty(property_name, state) self.label_widget.style().polish(self.label_widget) + self._state = state def item_value(self): return self.checkbox.isChecked() @@ -140,7 +141,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -163,6 +164,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_modified = False self._is_overriden = False + self._state = None + super(IntegerWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -223,25 +226,23 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset_value(self): self.set_value(self.origin_value) - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() - self.value_changed.emit() + self.value_changed.emit(self) - def _update_style(self): - is_overriden = self.is_overriden - if is_overriden and self.is_modified: - state = "overriden-modified" - elif is_overriden: - state = "overriden" - elif self.is_modified: - state = "modified" - else: - state = "original" + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return if self._as_widget: property_name = "input-state" @@ -261,7 +262,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -284,6 +285,8 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_modified = False self._is_overriden = False + self._state = None + super(FloatWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -354,25 +357,23 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value(0) - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() - self.value_changed.emit() + self.value_changed.emit(self) - def _update_style(self): - is_overriden = self.is_overriden - if is_overriden and self.is_modified: - state = "overriden-modified" - elif is_overriden: - state = "overriden" - elif self.is_modified: - state = "modified" - else: - state = "original" + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return if self._as_widget: property_name = "input-state" @@ -392,7 +393,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -415,6 +416,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_modified = False self._is_overriden = False + self._state = None + super(TextSingleLineWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -475,25 +478,23 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value("") - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() - self.value_changed.emit() + self.value_changed.emit(self) - def _update_style(self): - is_overriden = self.is_overriden - if is_overriden and self.is_modified: - state = "overriden-modified" - elif is_overriden: - state = "overriden" - elif self.is_modified: - state = "modified" - else: - state = "original" + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return if self._as_widget: property_name = "input-state" @@ -513,7 +514,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -535,6 +536,8 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_modified = False self._is_overriden = False + self._state = None + super(TextMultiLineWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -593,25 +596,23 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value("") - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() - self.value_changed.emit() + self.value_changed.emit(self) - def _update_style(self): - is_overriden = self.is_overriden - if is_overriden and self.is_modified: - state = "overriden-modified" - elif is_overriden: - state = "overriden" - elif self.is_modified: - state = "modified" - else: - state = "original" + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return if self._as_widget: property_name = "input-state" @@ -632,7 +633,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__(self, parent): super(TextListItem, self).__init__(parent) @@ -661,8 +662,8 @@ class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): self.is_single = False - def _on_value_change(self): - self.value_changed.emit() + def _on_value_change(self, item=None): + self.value_changed.emit(self) def row(self): return self.parent().input_fields.index(self) @@ -681,7 +682,7 @@ class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): super(TextListSubWidget, self).__init__(parent) @@ -723,8 +724,8 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value([]) - def _on_value_change(self): - self.value_changed.emit() + def _on_value_change(self, item=None): + self.value_changed.emit(self) def count(self): return len(self.input_fields) @@ -790,7 +791,7 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -812,6 +813,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self._is_overriden = False + self._state = None + super(TextListWidget, self).__init__(parent) self.setObjectName("TextListWidget") @@ -860,14 +863,14 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() - self.value_changed.emit() + self.value_changed.emit(self) def set_value(self, value, origin_value=False): self.value_widget.set_value(value) @@ -881,16 +884,14 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value([]) - def _update_style(self): - is_overriden = self.is_overriden - if is_overriden and self.is_modified: - state = "overriden-modified" - elif is_overriden: - state = "overriden" - elif self.is_modified: - state = "modified" - else: - state = "original" + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return self.label_widget.setProperty("state", state) self.label_widget.style().polish(self.label_widget) @@ -903,7 +904,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -928,6 +929,9 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._is_overriden = False self.is_group = is_group + self._state = None + self._child_state = None + super(DictExpandWidget, self).__init__(parent) self.setObjectName("DictExpandWidget") top_part = ClickableWidget(parent=self) @@ -1012,40 +1016,50 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def _on_value_change(self, value=None): - if self.is_group and self.is_overidable: - self._is_overriden = True + def _on_value_change(self, item=None): + if self.is_group: + if self.is_overidable: + self._is_overriden = True + # TODO update items + if item is not None: + is_overriden = self.is_overriden + for _item in self.input_fields: + if _item is not item: + _item.update_style(is_overriden) - self.value_changed.emit() + self.value_changed.emit(self) - self._update_style() + self.update_style() - def _update_style(self): + def update_style(self, is_overriden=None): child_modified = self.child_modified - child_overriden = self.child_overriden + if is_overriden is None: + child_overriden = self.child_overriden + child_state = self.style_state(child_overriden, child_modified) + if child_state: + child_state = "child-{}".format(child_state) - if child_modified and child_overriden: - widget_state = "child-overriden-modified" - elif child_modified: - widget_state = "child-modified" - elif child_overriden: - widget_state = "child-overriden" + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + if is_overriden is None: + is_overriden = self.is_overriden + + if child_modified and not is_overriden: + state = self.default_state else: - widget_state = "" + state = self.style_state(is_overriden, child_modified) - self.setProperty("state", widget_state) - self.style().polish(self) - - if child_modified and self.is_overriden: - state = "overriden-modified" - elif self.is_overriden: - state = "overriden" - else: - state = "original" + if self._state == state: + return self.button_toggle_text.setProperty("state", state) self.button_toggle_text.style().polish(self.button_toggle_text) + self._state = state + @property def child_modified(self): for input_field in self.input_fields: @@ -1181,7 +1195,7 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): class DictFormWidget(QtWidgets.QWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -1208,8 +1222,8 @@ class DictFormWidget(QtWidgets.QWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) - def _on_value_change(self): - self.value_changed.emit() + def _on_value_change(self, item=None): + self.value_changed.emit(self) def item_value(self): output = {} @@ -1261,7 +1275,7 @@ class DictFormWidget(QtWidgets.QWidget): class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__(self, object_type, parent): self._parent = parent @@ -1311,9 +1325,9 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def _key(self): return self.key_input.text() - def _on_value_change(self): - self._update_style() - self.value_changed.emit() + def _on_value_change(self, item=None): + self.update_style() + self.value_changed.emit(self) @property def is_group(self): @@ -1341,16 +1355,11 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def is_modified(self): return self.is_value_modified() or self.is_key_modified() - def _update_style(self): - # if self._is_overidable and self.is_overriden: - # if is_modified: - # state = "overriden-modified" - # else: - # state = "overriden" + def update_style(self, is_overriden=None): if self.is_key_modified(): state = "modified" else: - state = "original" + state = "" self.key_input.setProperty("state", state) self.key_input.style().polish(self.key_input) @@ -1377,7 +1386,7 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): self._parent = parent @@ -1424,8 +1433,8 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def any_parent_is_group(self): return self._parent.any_parent_is_group - def _on_value_change(self): - self.value_changed.emit() + def _on_value_change(self, item=None): + self.value_changed.emit(self) def count(self): return len(self.input_fields) @@ -1503,6 +1512,9 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() + if not any_parent_is_group and not is_group: + is_group = True + self.any_parent_is_group = any_parent_is_group self.is_modified = False @@ -1525,12 +1537,12 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.origin_value = self.item_value() - def _on_value_change(self, value=None): + def _on_value_change(self, item=None): self.child_modified = self.item_value() != self.origin_value if self.is_group and self.is_overidable: self._is_overriden = True - self._update_style() + self.update_style() @property def child_overriden(self): @@ -1546,32 +1558,29 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def _update_style(self): + def update_style(self, is_overriden=None): child_modified = self.child_modified - child_overriden = self.child_overriden + if is_overriden is None: + child_overriden = self.child_overriden + child_state = self.style_state(child_overriden, child_modified) + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state - if child_modified and child_overriden: - widget_state = "child-overriden-modified" - elif child_modified: - widget_state = "child-modified" - elif child_overriden: - widget_state = "child-overriden" + if is_overriden is None: + is_overriden = self.is_overriden + + if child_modified and not is_overriden: + state = self.default_state else: - widget_state = "" - - self.setProperty("state", widget_state) - self.style().polish(self) - - if child_modified and self.is_overriden: - state = "overriden-modified" - elif self.is_overriden: - state = "overriden" - else: - state = "original" + state = self.style_state(self.is_overriden, child_modified) self.label_widget.setProperty("state", state) self.label_widget.style().polish(self.label_widget) + self._state = state + def item_value(self): return self.value_widget.config_value() From 1038619bebb4aefcf65cd0c9c7cd3ae45e066dcd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 28 Jul 2020 19:39:46 +0200 Subject: [PATCH 204/947] change the way job farm type is determined --- .../publish/submit_celaction_deadline.py | 1 + pype/plugins/fusion/publish/submit_deadline.py | 2 +- .../global/publish/submit_publish_job.py | 18 +++++++++--------- pype/plugins/maya/publish/collect_render.py | 1 + .../maya/publish/submit_maya_deadline.py | 12 +++++++++--- .../plugins/maya/publish/submit_maya_muster.py | 1 + .../nuke/publish/submit_nuke_deadline.py | 1 + 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 9091b24150..de764e3b33 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -34,6 +34,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): ] def process(self, instance): + instance.data["toBeRenderedOn"] = "deadline" context = instance.context DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL") diff --git a/pype/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py index e5deb1b070..0dd34ba713 100644 --- a/pype/plugins/fusion/publish/submit_deadline.py +++ b/pype/plugins/fusion/publish/submit_deadline.py @@ -22,7 +22,7 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): families = ["saver.deadline"] def process(self, instance): - + instance.data["toBeRenderedOn"] = "deadline" context = instance.context key = "__hasRun{}".format(self.__class__.__name__) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 838b1717c2..21dcf93cdb 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -855,17 +855,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ''' - render_job = data.pop("deadlineSubmissionJob", None) - submission_type = "deadline" - if not render_job: - # No deadline job. Try Muster: musterSubmissionJob + if instance.data.get("toBeRenderedOn") == "deadline": + render_job = data.pop("deadlineSubmissionJob", None) + submission_type = "deadline" + + if instance.data.get("toBeRenderedOn") == "muster": render_job = data.pop("musterSubmissionJob", None) submission_type = "muster" - assert render_job or instance.data.get("tileRendering") is False, ( - "Can't continue without valid Deadline " - "or Muster submission prior to this " - "plug-in." - ) + + if not render_job and instance.data.get("tileRendering") is False: + raise AssertionError(("Cannot continue without valid Deadline " + "or Muster submission.")) if submission_type == "deadline": self.DEADLINE_REST_URL = os.environ.get( diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 03b14f76bb..75567ae216 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -242,6 +242,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "resolutionWidth": cmds.getAttr("defaultResolution.width"), "resolutionHeight": cmds.getAttr("defaultResolution.height"), "pixelAspect": cmds.getAttr("defaultResolution.pixelAspect"), + "tileRendering": render_instance.data.get("tileRendering") or False # noqa: E501 } # Apply each user defined attribute as data diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 3a6d12f623..5c13f6e62d 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -20,6 +20,7 @@ import os import json import getpass import copy +import re import clique import requests @@ -85,7 +86,8 @@ def get_renderer_variables(renderlayer, root): gin="#" * int(padding), lut=True, layer=renderlayer or lib.get_current_renderlayer())[0] - filename_0 = filename_0.replace('_', '_beauty') + filename_0 = re.sub('_', '_beauty', + filename_0, flags=re.IGNORECASE) prefix_attr = "defaultRenderGlobals.imageFilePrefix" if renderer == "vray": renderlayer = renderlayer.split("_")[-1] @@ -108,8 +110,8 @@ def get_renderer_variables(renderlayer, root): # does not work for vray. scene = cmds.file(query=True, sceneName=True) scene, _ = os.path.splitext(os.path.basename(scene)) - filename_0 = filename_prefix.replace('', scene) - filename_0 = filename_0.replace('', renderlayer) + filename_0 = re.sub('', scene, filename_prefix, flags=re.IGNORECASE) # noqa: E501 + filename_0 = re.sub('', renderlayer, filename_0, flags=re.IGNORECASE) # noqa: E501 filename_0 = "{}.{}.{}".format( filename_0, "#" * int(padding), extension) filename_0 = os.path.normpath(os.path.join(root, filename_0)) @@ -164,6 +166,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): def process(self, instance): """Plugin entry point.""" + instance.data["toBeRenderedOn"] = "deadline" self._instance = instance self._deadline_url = os.environ.get( "DEADLINE_REST_URL", "http://localhost:8082") @@ -172,6 +175,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): context = instance.context workspace = context.data["workspaceDir"] anatomy = context.data['anatomy'] + instance.data["toBeRenderedOn"] = "deadline" filepath = None @@ -402,6 +406,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if not response.ok: raise Exception(response.text) instance.data["deadlineSubmissionJob"] = response.json() + else: + self.log.info("Skipping submission, tile rendering enabled.") # Store output dir for unified publisher (filesequence) instance.data["outputDir"] = os.path.dirname(output_filename_0) diff --git a/pype/plugins/maya/publish/submit_maya_muster.py b/pype/plugins/maya/publish/submit_maya_muster.py index 5a2e578793..ffe434048a 100644 --- a/pype/plugins/maya/publish/submit_maya_muster.py +++ b/pype/plugins/maya/publish/submit_maya_muster.py @@ -249,6 +249,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): Authenticate with Muster, collect all data, prepare path for post render publish job and submit job to farm. """ + instance.data["toBeRenderedOn"] = "muster" # setup muster environment self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL") diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 26d3f9b571..2c7d468d3a 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -28,6 +28,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): deadline_chunk_size = 1 def process(self, instance): + instance.data["toBeRenderedOn"] = "deadline" families = instance.data["families"] node = instance[0] From edf61b7c28781b710dd3ce1f5495ec21e2002ed6 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 29 Jul 2020 10:51:23 +0200 Subject: [PATCH 205/947] bump version to 2.11.1 --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index 7f6646a762..200c236308 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.11.0" +__version__ = "2.11.1" From 37b2188919d5744c2cf515962e0aab6be3cf0c58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Jul 2020 14:20:40 +0200 Subject: [PATCH 206/947] location_path is prepared for refilling the root key --- pype/modules/ftrack/actions/action_delivery.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index d4b86d1278..7ae7de65b1 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -293,6 +293,20 @@ class Delivery(BaseAction): repres_to_deliver.append(repre) anatomy = Anatomy(project_name) + + format_dict = {} + if location_path: + location_path = location_path.replace("\\", "/") + root_names = anatomy.root_names_from_templates( + anatomy.templates["delivery"] + ) + if root_names is None: + format_dict["root"] = location_path + else: + format_dict["root"] = {} + for name in root_names: + format_dict["root"][name] = location_path + for repre in repres_to_deliver: # Get destination repre path anatomy_data = copy.deepcopy(repre["context"]) From 7b7d4aa7a8fc59fef0d9523e5b3ae763a897056e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Jul 2020 14:21:26 +0200 Subject: [PATCH 207/947] processing methods format path with entered location --- .../modules/ftrack/actions/action_delivery.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 7ae7de65b1..a50603b2eb 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -368,10 +368,15 @@ class Delivery(BaseAction): return self.report() def process_single_file( - self, repre_path, anatomy, anatomy_name, anatomy_data + self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict ): anatomy_filled = anatomy.format(anatomy_data) - delivery_path = anatomy_filled["delivery"][anatomy_name] + if format_dict: + template_result = anatomy_filled["delivery"][anatomy_name] + delivery_path = template_result.rootless.format(**format_dict) + else: + delivery_path = anatomy_filled["delivery"][anatomy_name] + delivery_folder = os.path.dirname(delivery_path) if not os.path.exists(delivery_folder): os.makedirs(delivery_folder) @@ -379,7 +384,7 @@ class Delivery(BaseAction): self.copy_file(repre_path, delivery_path) def process_sequence( - self, repre_path, anatomy, anatomy_name, anatomy_data + self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict ): dir_path, file_name = os.path.split(str(repre_path)) @@ -422,8 +427,12 @@ class Delivery(BaseAction): anatomy_data["frame"] = frame_indicator anatomy_filled = anatomy.format(anatomy_data) - delivery_path = anatomy_filled["delivery"][anatomy_name] - print(delivery_path) + if format_dict: + template_result = anatomy_filled["delivery"][anatomy_name] + delivery_path = template_result.rootless.format(**format_dict) + else: + delivery_path = anatomy_filled["delivery"][anatomy_name] + delivery_folder = os.path.dirname(delivery_path) dst_head, dst_tail = delivery_path.split(frame_indicator) dst_padding = src_collection.padding From bb9e5ef4f90d00a8a86f26ea6d195f7a8c2aaee5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Jul 2020 14:21:57 +0200 Subject: [PATCH 208/947] args for proces methods are pre-pared --- pype/modules/ftrack/actions/action_delivery.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index a50603b2eb..06257f32d5 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -353,15 +353,18 @@ class Delivery(BaseAction): repre_path = self.path_from_represenation(repre, anatomy) # TODO add backup solution where root of path from component # is repalced with root - if not frame: - self.process_single_file( - repre_path, anatomy, anatomy_name, anatomy_data - ) + args = ( + repre_path, + anatomy, + anatomy_name, + anatomy_data, + format_dict + ) + if not frame: + self.process_single_file(*args) else: - self.process_sequence( - repre_path, anatomy, anatomy_name, anatomy_data - ) + self.process_sequence(*args) self.db_con.uninstall() From e2b3f7496c40b30322f0467c9505dab6c8e4c394 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 29 Jul 2020 14:47:17 +0200 Subject: [PATCH 209/947] submiter validation, respect render priority --- .../global/publish/submit_publish_job.py | 59 ++++++++++++---- pype/plugins/maya/publish/collect_render.py | 3 +- .../maya/publish/submit_maya_deadline.py | 4 ++ .../validate_deadline_tile_submission.py | 69 +++++++++++++++++++ .../maya/publish/validate_frame_range.py | 6 ++ 5 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 pype/plugins/maya/publish/validate_deadline_tile_submission.py diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 21dcf93cdb..43edc33cba 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -232,7 +232,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): return (metadata_path, roothless_mtdt_p) - def _submit_deadline_post_job(self, instance, job): + def _submit_deadline_post_job(self, instance, job, instances): """Submit publish job to Deadline. Deadline specific code separated from :meth:`process` for sake of @@ -281,24 +281,28 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): and we are just waiting for them. """ if instance.data.get("tileRendering"): + self.log.info("Adding tile assembly results as dependencies...") asset_index = 0 - for represenation in instance.data.get("representations", []): - if isinstance(represenation["files"], [list, tuple]): - for file in represenation["files"]: + for inst in instances: + for represenation in inst.get("representations", []): + self.log.debug( + "working on {}".format(represenation["name"])) + if isinstance(represenation["files"], (list, tuple)): + for file in represenation["files"]: + self.log.debug("adding {}".format(file)) + dependency = os.path.join(output_dir, file) + payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 + else: dependency = os.path.join(output_dir, file) payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 - asset_index += 1 - else: - dependency = os.path.join(output_dir, file) - payload["JobInfo"]["AssetDependency0"] = dependency + asset_index += 1 else: - payload["JobInfo"]["JobDependency0"] = job["_id"], + payload["JobInfo"]["JobDependency0"] = job["_id"] # Transfer the environment from the original job to this dependent # job so they use the same environment metadata_path, roothless_metadata_path = self._create_metadata_path( instance) - environment = job["Props"].get("Env", {}) environment["PYPE_METADATA_FILE"] = roothless_metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] @@ -847,14 +851,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): r''' SUBMiT PUBLiSH JOB 2 D34DLiN3 ____ - ' ' .---. .---. .--. .---. .--..--..--..--. .---. - | | --= \ | . \/ _|/ \| . \ || || \ |/ _| - | JOB | --= / | | || __| .. | | | |;_ || \ || __| - | | |____./ \.__|._||_.|___./|_____|||__|\__|\.___| + ' ' .---. .---. .--. .---. .--..--..--..--. .---. + | | --= \ | . \/ _|/ \| . \ || || \ |/ _| + | JOB | --= / | | || __| .. | | | |;_ || \ || __| + | | |____./ \.__|._||_.|___./|_____|||__|\__|\.___| ._____. ''' + render_job = None if instance.data.get("toBeRenderedOn") == "deadline": render_job = data.pop("deadlineSubmissionJob", None) submission_type = "deadline" @@ -867,13 +872,37 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): raise AssertionError(("Cannot continue without valid Deadline " "or Muster submission.")) + if not render_job: + import getpass + + render_job = {} + self.log.info("Faking job data ...") + render_job["Props"] = {} + # Render job doesn't exist because we do not have prior submission. + # We still use data from it so lets fake it. + # + # Batch name reflect original scene name + render_job["Props"]["Batch"] = os.path.splitext(os.path.basename( + context.data.get("currentFile")))[0] + # User is deadline user + render_job["Props"]["User"] = context.data.get( + "deadlineUser", getpass.getuser()) + # Priority is now not handled at all + render_job["Props"]["Pri"] = instance.data.get("priority") + + render_job["Props"]["Env"] = { + "FTRACK_API_USER": os.environ.get("FTRACK_API_USER"), + "FTRACK_API_KEY": os.environ.get("FTRACK_API_KEY"), + "FTRACK_SERVER": os.environ.get("FTRACK_SERVER"), + } + if submission_type == "deadline": self.DEADLINE_REST_URL = os.environ.get( "DEADLINE_REST_URL", "http://localhost:8082" ) assert self.DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" - self._submit_deadline_post_job(instance, render_job) + self._submit_deadline_post_job(instance, render_job, instances) # publish job file publish_job = { diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 75567ae216..5ca9392080 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -242,7 +242,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "resolutionWidth": cmds.getAttr("defaultResolution.width"), "resolutionHeight": cmds.getAttr("defaultResolution.height"), "pixelAspect": cmds.getAttr("defaultResolution.pixelAspect"), - "tileRendering": render_instance.data.get("tileRendering") or False # noqa: E501 + "tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501 + "priority": render_instance.data.get("priority") } # Apply each user defined attribute as data diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 5c13f6e62d..5840a7b946 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -45,6 +45,7 @@ payload_skeleton = { "Plugin": "MayaPype", "Frames": "{start}-{end}x{step}", "Comment": None, + "Priority": 50, }, "PluginInfo": { "SceneFile": None, # Input @@ -302,6 +303,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload_skeleton["JobInfo"]["Name"] = jobname # Arbitrary username, for visualisation in Monitor payload_skeleton["JobInfo"]["UserName"] = deadline_user + # Set job priority + payload_skeleton["JobInfo"]["Priority"] = self._instance.data.get( + "priority", 50) # Optional, enable double-click to preview rendered # frames from Deadline Monitor payload_skeleton["JobInfo"]["OutputDirectory0"] = \ diff --git a/pype/plugins/maya/publish/validate_deadline_tile_submission.py b/pype/plugins/maya/publish/validate_deadline_tile_submission.py new file mode 100644 index 0000000000..b0b995de3e --- /dev/null +++ b/pype/plugins/maya/publish/validate_deadline_tile_submission.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +"""Validate settings from Deadline Submitter. + +This is useful mainly for tile rendering, where jobs on farm are created by +submitter script from Maya. + +Unfortunately Deadline doesn't expose frame number for tiles job so that +cannot be validated, even if it is important setting. Also we cannot +determine if 'Region Rendering' (tile rendering) is enabled or not because +of the same thing. + +""" +import os + +from maya import mel +from maya import cmds + +import pyblish.api +from pype.hosts.maya import lib + + +class ValidateDeadlineTileSubmission(pyblish.api.InstancePlugin): + """Validate Deadline Submission settings are OK for tile rendering.""" + + label = "Validate Deadline Tile Submission" + order = pyblish.api.ValidatorOrder + hosts = ["maya"] + families = ["renderlayer"] + if not os.environ.get("DEADLINE_REST_URL"): + active = False + + def process(self, instance): + """Entry point.""" + # try if Deadline submitter was loaded + if mel.eval("exists SubmitJobToDeadline") == 0: + # if not, try to load it manually + try: + mel.eval("source DeadlineMayaClient;") + except RuntimeError: + raise AssertionError("Deadline Maya client cannot be loaded") + mel.eval("DeadlineMayaClient();") + assert mel.eval("exists SubmitJobToDeadline") == 1, ( + "Deadline Submission script cannot be initialized.") + if instance.data.get("tileRendering"): + job_name = cmds.getAttr("defaultRenderGlobals.deadlineJobName") + scene_name = os.path.splitext(os.path.basename( + instance.context.data.get("currentFile")))[0] + if job_name != scene_name: + self.log.warning(("Job submitted through Deadline submitter " + "has different name then current scene " + "{} / {}").format(job_name, scene_name)) + if cmds.getAttr("defaultRenderGlobals.deadlineTileSingleJob") == 1: + layer = instance.data['setMembers'] + anim_override = lib.get_attr_in_layer( + "defaultRenderGlobals.animation", layer=layer) + assert anim_override, ( + "Animation must be enabled in " + "Render Settings even when rendering single frame." + ) + + start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") + end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") + assert start_frame == end_frame, ( + "Start frame and end frame are not equals. When " + "'Submit All Tles As A Single Job' is selected, only " + "single frame is expected to be rendered. It must match " + "the one specified in Deadline Submitter under " + "'Region Rendering'" + ) diff --git a/pype/plugins/maya/publish/validate_frame_range.py b/pype/plugins/maya/publish/validate_frame_range.py index 0d51a83cf5..1ee6e2bd25 100644 --- a/pype/plugins/maya/publish/validate_frame_range.py +++ b/pype/plugins/maya/publish/validate_frame_range.py @@ -29,6 +29,12 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context + if instance.data.get("tileRendering"): + self.log.info(( + "Skipping frame range validation because " + "tile rendering is enabled." + )) + return frame_start_handle = int(context.data.get("frameStartHandle")) frame_end_handle = int(context.data.get("frameEndHandle")) From a39f975a8fa8ea6c75ebaf38aba2999d6d1f81fe Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 29 Jul 2020 14:53:43 +0200 Subject: [PATCH 210/947] removed debug prints, fixed single file handling --- pype/plugins/global/publish/submit_publish_job.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 4ac7d5d73a..b6d62a8fd1 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -285,15 +285,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): asset_index = 0 for inst in instances: for represenation in inst.get("representations", []): - self.log.debug( - "working on {}".format(represenation["name"])) if isinstance(represenation["files"], (list, tuple)): for file in represenation["files"]: - self.log.debug("adding {}".format(file)) dependency = os.path.join(output_dir, file) payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 else: - dependency = os.path.join(output_dir, file) + dependency = os.path.join( + output_dir, represenation["files"]) payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 asset_index += 1 else: From ec7bbcf19e4fe59437f8daee3ba3c591e25ab6d0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Jul 2020 15:55:22 +0200 Subject: [PATCH 211/947] small bugfixes and added rawjson widget --- .../config_gui_schema/plugins_gui_schema.json | 24 +++ .../config_gui_schema/project_gui_schema.json | 3 +- pype/tools/config_setting/widgets/base.py | 57 ++++--- pype/tools/config_setting/widgets/inputs.py | 139 +++++++++++++++++- 4 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json b/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json new file mode 100644 index 0000000000..79c1f85b85 --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json @@ -0,0 +1,24 @@ +{ + "key": "plugins", + "type": "dict-expanding", + "label": "Plugins", + "children": [ + { + "key": "maya", + "type": "dict-expanding", + "label": "Maya", + "is_group": true, + "children": [ + { + "key": "test1", + "type": "raw-json", + "label": "Test1" + }, { + "key": "test2", + "type": "raw-json", + "label": "Test2" + } + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json index 38c07ec33d..d2a6221f99 100644 --- a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json @@ -6,7 +6,8 @@ { "type": "schema", "children": [ - "ftrack_projects_gui_schema" + "ftrack_projects_gui_schema", + "plugins_gui_schema" ] } ] diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index e01f14aa70..52184b4c8a 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -177,6 +177,7 @@ class ProjectListView(QtWidgets.QListView): class ProjectListWidget(QtWidgets.QWidget): default = "< Default >" + project_changed = QtCore.Signal() def __init__(self, parent): self._parent = parent @@ -218,6 +219,10 @@ class ProjectListWidget(QtWidgets.QWidget): if self.validate_context_change(): self.select_project(new_project_name) self.current_project = new_project_name + self.project_changed.emit() + return + + self.select_project(self.current_project) def validate_context_change(self): # TODO add check if project can be changed (is modified) @@ -275,7 +280,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) - self.is_overidable = True + self.is_overidable = False self.input_fields = [] scroll_widget = QtWidgets.QScrollArea(self) @@ -292,23 +297,13 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): project_list_widget = ProjectListWidget(self) content_layout.addWidget(project_list_widget) - self.project_list_widget = project_list_widget - self.scroll_widget = scroll_widget - self.content_layout = content_layout - self.content_widget = content_widget - - values = config.project_presets() - schema = config.gui_schema("project_gui_schema") - self.keys = schema.get("keys", []) - self.add_children_gui(schema, values) - footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) - btn = QtWidgets.QPushButton("Finish") + save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) - footer_layout.addWidget(btn, 0) + footer_layout.addWidget(save_btn, 0) presets_widget = QtWidgets.QWidget() presets_layout = QtWidgets.QVBoxLayout(presets_widget) @@ -326,18 +321,18 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): layout.addWidget(project_list_widget, 0) layout.addWidget(presets_widget, 1) - btn.clicked.connect(self.___finish) + save_btn.clicked.connect(self._save) + project_list_widget.project_changed.connect(self._on_project_change) - def ___finish(self): - output = {} - for item in self.input_fields: - output.update(item.config_value()) + self.project_list_widget = project_list_widget + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget - for key in reversed(self.keys): - _output = {key: output} - output = _output - - print(json.dumps(output, indent=4)) + values = config.global_project_presets() + schema = config.gui_schema("project_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] @@ -348,3 +343,19 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): ) self.input_fields.append(item) self.content_layout.addWidget(item) + + def _on_project_change(self): + self.is_overidable = ( + self.project_list_widget.project_name() is not None + ) + + def _save(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + print(json.dumps(output, indent=4)) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index e5b929b7ca..58898aeccb 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1,5 +1,5 @@ import json -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui from . import config from .base import PypeConfigurationWidget, TypeToKlass from .widgets import ( @@ -520,6 +520,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent + self._as_widget = values is AS_WIDGET any_parent_is_group = parent.is_group if not any_parent_is_group: @@ -631,6 +632,141 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: self.item_value()} +class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self.is_modified = False + self._is_overriden = False + + self._state = None + + super(RawJsonWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QPlainTextEdit() + self.text_input.setTabStopDistance( + QtGui.QFontMetricsF( + self.text_input.font() + ).horizontalAdvance(" ") * 4 + ) + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setPlainText(value) + + self.origin_value = self.item_value() + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + if self._is_overriden: + return self._is_overriden + return self._parent.is_overriden + + def validate_value(self, value): + try: + json.dumps(value) + return True + except Exception: + return False + + def set_value(self, value, origin_value=False): + is_valid = self.validate_value(value) + if is_valid: + value = json.dumps(value, indent=4) + self.text_input.setPlainText(value) + + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value("{{}}") + + def _on_value_change(self, item=None): + self.is_modified = self.item_value() != self.origin_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self, is_overriden=None): + if is_overriden is None: + is_overriden = self.is_overriden + is_modified = self.is_modified + + state = self.style_state(is_overriden, is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1591,6 +1727,7 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["text-singleline"] = TextSingleLineWidget TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget TypeToKlass.types["float"] = FloatWidget TypeToKlass.types["dict-expanding"] = DictExpandWidget From 0ab6161193abfc2066edc230d143bb4064a055e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 29 Jul 2020 16:07:45 +0200 Subject: [PATCH 212/947] minor changes in raw json --- pype/tools/config_setting/style/style.css | 4 ++++ pype/tools/config_setting/widgets/inputs.py | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css index aabffc3f84..adf8344f89 100644 --- a/pype/tools/config_setting/style/style.css +++ b/pype/tools/config_setting/style/style.css @@ -57,6 +57,10 @@ QPushButton[btn-type="expand-toggle"] { background: transparent; } +#RawJson[state="invalid"] { + border-color: #ff5511; +} + #DictKey[state="modified"] { border-color: #137cbd; } diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 58898aeccb..50bd3d58f4 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -658,6 +658,8 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._state = None + self.is_valid = None + super(RawJsonWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) @@ -665,6 +667,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): layout.setSpacing(0) self.text_input = QtWidgets.QPlainTextEdit() + self.text_input.setObjectName("RawJson") self.text_input.setTabStopDistance( QtGui.QFontMetricsF( self.text_input.font() @@ -710,6 +713,8 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def validate_value(self, value): + if not value: + return True try: json.dumps(value) return True @@ -720,6 +725,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_valid = self.validate_value(value) if is_valid: value = json.dumps(value, indent=4) + self.text_input.setPlainText(value) if origin_value: @@ -730,13 +736,23 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.origin_value) def clear_value(self): - self.set_value("{{}}") + self.set_value("") def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + value = self.item_value() + self.is_modified = value != self.origin_value if self.is_overidable: self._is_overriden = True + is_valid = self.validate_value(value) + if is_valid != self.is_valid: + self.is_valid = is_valid + if is_valid: + state = "" + else: + state = "invalid" + self.text_input.setProperty("state", state) + self.text_input.style().polish(self.text_input) self.update_style() self.value_changed.emit(self) From 832fd9fa66b67c5a6dacb7817745f84cfbea4e4b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 29 Jul 2020 16:17:15 +0100 Subject: [PATCH 213/947] Fix multiple attributes on the same node overwriting. --- pype/plugins/maya/publish/validate_attributes.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pype/plugins/maya/publish/validate_attributes.py b/pype/plugins/maya/publish/validate_attributes.py index 6ecebfa107..a77fbe5e93 100644 --- a/pype/plugins/maya/publish/validate_attributes.py +++ b/pype/plugins/maya/publish/validate_attributes.py @@ -62,9 +62,16 @@ class ValidateAttributes(pyblish.api.ContextPlugin): for family in families: for preset in presets[family]: [node_name, attribute_name] = preset.split(".") - attributes.update( - {node_name: {attribute_name: presets[family][preset]}} - ) + try: + attributes[node_name].update( + {attribute_name: presets[family][preset]} + ) + except KeyError: + attributes.update({ + node_name: { + attribute_name: presets[family][preset] + } + }) # Get invalid attributes. nodes = pm.ls() From 3925ddc5aeabe392230224247bd909c73031fed2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 29 Jul 2020 17:34:54 +0100 Subject: [PATCH 214/947] Instance in Same Context for Nuke Moved the equivalent Maya plugin into global and merged with Nuke. Fixed Nuke instances collection not using node data for asset. --- .../publish/validate_instance_in_context.py | 133 ++++++++++++++++++ .../publish/validate_instance_in_context.py | 108 -------------- .../plugins/nuke/publish/collect_instances.py | 2 +- 3 files changed, 134 insertions(+), 109 deletions(-) create mode 100644 pype/plugins/global/publish/validate_instance_in_context.py delete mode 100644 pype/plugins/maya/publish/validate_instance_in_context.py diff --git a/pype/plugins/global/publish/validate_instance_in_context.py b/pype/plugins/global/publish/validate_instance_in_context.py new file mode 100644 index 0000000000..a4fc555161 --- /dev/null +++ b/pype/plugins/global/publish/validate_instance_in_context.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +"""Validate if instance asset is the same as context asset.""" +from __future__ import absolute_import + +import pyblish.api +import pype.api + + +class SelectInvalidInstances(pyblish.api.Action): + """Select invalid instances in Outliner.""" + + label = "Select Instances" + icon = "briefcase" + on = "failed" + + def process(self, context, plugin): + """Process invalid validators and select invalid instances.""" + # Get the errored instances + failed = [] + for result in context.data["results"]: + if result["error"] is None: + continue + if result["instance"] is None: + continue + if result["instance"] in failed: + continue + if result["plugin"] != plugin: + continue + + failed.append(result["instance"]) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(failed, plugin) + + if instances: + self.log.info( + "Selecting invalid nodes: %s" % ", ".join( + [str(x) for x in instances] + ) + ) + self.select(instances) + else: + self.log.info("No invalid nodes found.") + self.deselect() + + def select(self, instances): + if "nuke" in pyblish.api.registered_hosts(): + import avalon.nuke.lib + import nuke + avalon.nuke.lib.select_nodes( + [nuke.toNode(str(x)) for x in instances] + ) + + if "maya" in pyblish.api.registered_hosts(): + from maya import cmds + cmds.select(instances, replace=True, noExpand=True) + + def deselect(self): + if "nuke" in pyblish.api.registered_hosts(): + import avalon.nuke.lib + avalon.nuke.lib.reset_selection() + + if "maya" in pyblish.api.registered_hosts(): + from maya import cmds + cmds.select(deselect=True) + + +class RepairSelectInvalidInstances(pyblish.api.Action): + """Repair the instance asset.""" + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + # Get the errored instances + failed = [] + for result in context.data["results"]: + if result["error"] is None: + continue + if result["instance"] is None: + continue + if result["instance"] in failed: + continue + if result["plugin"] != plugin: + continue + + failed.append(result["instance"]) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(failed, plugin) + + context_asset = context.data["assetEntity"]["name"] + for instance in instances: + self.set_attribute(instance, context_asset) + + def set_attribute(self, instance, context_asset): + if "nuke" in pyblish.api.registered_hosts(): + import nuke + nuke.toNode( + instance.data.get("name") + )["avalon:asset"].setValue(context_asset) + + if "maya" in pyblish.api.registered_hosts(): + from maya import cmds + cmds.setAttr( + instance.data.get("name") + ".asset", + context_asset, + type="string" + ) + + +class ValidateInstanceInContext(pyblish.api.InstancePlugin): + """Validator to check if instance asset match context asset. + + When working in per-shot style you always publish data in context of + current asset (shot). This validator checks if this is so. It is optional + so it can be disabled when needed. + + Action on this validator will select invalid instances in Outliner. + """ + + order = pype.api.ValidateContentsOrder + label = "Instance in same Context" + optional = True + hosts = ["maya", "nuke"] + actions = [SelectInvalidInstances, RepairSelectInvalidInstances] + + def process(self, instance): + asset = instance.data.get("asset") + context_asset = instance.context.data["assetEntity"]["name"] + msg = "{} has asset {}".format(instance.name, asset) + assert asset == context_asset, msg diff --git a/pype/plugins/maya/publish/validate_instance_in_context.py b/pype/plugins/maya/publish/validate_instance_in_context.py deleted file mode 100644 index 542249bb2d..0000000000 --- a/pype/plugins/maya/publish/validate_instance_in_context.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validate if instance asset is the same as context asset.""" -from __future__ import absolute_import -import pyblish.api -from pype.action import get_errored_instances_from_context -import pype.api - - -class SelectInvalidInstances(pyblish.api.Action): - """Select invalid instances in Outliner.""" - - label = "Show Instances" - icon = "briefcase" - on = "failed" - - def process(self, context, plugin): - """Process invalid validators and select invalid instances.""" - try: - from maya import cmds - except ImportError: - raise ImportError("Current host is not Maya") - - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - - # Get the invalid nodes for the plug-ins - self.log.info("Finding invalid nodes..") - invalid = list() - for _instance in instances: - invalid_instances = plugin.get_invalid(context) - if invalid_instances: - if isinstance(invalid_instances, (list, tuple)): - invalid.extend(invalid_instances) - else: - self.log.warning("Plug-in returned to be invalid, " - "but has no selectable nodes.") - - # Ensure unique (process each node only once) - invalid = list(set(invalid)) - - if invalid: - self.log.info("Selecting invalid nodes: %s" % ", ".join(invalid)) - cmds.select(invalid, replace=True, noExpand=True) - else: - self.log.info("No invalid nodes found.") - cmds.select(deselect=True) - - -class RepairSelectInvalidInstances(pyblish.api.Action): - """Repair the instance asset.""" - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - from maya import cmds - # Get the errored instances - failed = [] - for result in context.data["results"]: - if (result["error"] is not None and result["instance"] is not None - and result["instance"] not in failed): - failed.append(result["instance"]) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(failed, plugin) - context_asset = context.data["assetEntity"]["name"] - for instance in instances: - cmds.setAttr(instance.data.get("name") + ".asset", - context_asset, type="string") - - -class ValidateInstanceInContext(pyblish.api.ContextPlugin): - """Validator to check if instance asset match context asset. - - When working in per-shot style you always publish data in context of - current asset (shot). This validator checks if this is so. It is optional - so it can be disabled when needed. - - Action on this validator will select invalid instances in Outliner. - """ - - order = pype.api.ValidateContentsOrder - label = "Instance in same Context" - optional = True - actions = [SelectInvalidInstances, RepairSelectInvalidInstances] - - @classmethod - def get_invalid(cls, context): - """Get invalid instances.""" - invalid = [] - context_asset = context.data["assetEntity"]["name"] - cls.log.info("we are in {}".format(context_asset)) - for instance in context: - asset = instance.data.get("asset") - if asset != context_asset: - cls.log.warning("{} has asset {}".format(instance.name, asset)) - invalid.append(instance.name) - - return invalid - - def process(self, context): - """Check instances.""" - invalid = self.get_invalid(context) - if invalid: - raise AssertionError("Some instances doesn't share same context") diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 0bbede11c0..9085e12bd8 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -106,7 +106,7 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): instance.data.update({ "subset": subset, - "asset": os.environ["AVALON_ASSET"], + "asset": avalon_knob_data["asset"], "label": node.name(), "name": node.name(), "subset": subset, From 907ebe17604142a2487879a9004247bc81cf2c54 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 30 Jul 2020 08:46:53 +0100 Subject: [PATCH 215/947] Fix viewer input process node return as Viewer node --- pype/hosts/nuke/lib.py | 2 +- pype/plugins/nuke/publish/extract_thumbnail.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/hosts/nuke/lib.py b/pype/hosts/nuke/lib.py index 72a8836a03..8c0e37b15d 100644 --- a/pype/hosts/nuke/lib.py +++ b/pype/hosts/nuke/lib.py @@ -1445,7 +1445,7 @@ class ExporterReview: anlib.reset_selection() ipn_orig = None for v in [n for n in nuke.allNodes() - if "Viewer" in n.Class()]: + if "Viewer" == n.Class()]: ip = v['input_process'].getValue() ipn = v['input_process_node'].getValue() if "VIEWER_INPUT" not in ipn and ip: diff --git a/pype/plugins/nuke/publish/extract_thumbnail.py b/pype/plugins/nuke/publish/extract_thumbnail.py index 5e9302a01a..a3ef09bc9f 100644 --- a/pype/plugins/nuke/publish/extract_thumbnail.py +++ b/pype/plugins/nuke/publish/extract_thumbnail.py @@ -152,7 +152,7 @@ class ExtractThumbnail(pype.api.Extractor): ipn_orig = None for v in [n for n in nuke.allNodes() - if "Viewer" in n.Class()]: + if "Viewer" == n.Class()]: ip = v['input_process'].getValue() ipn = v['input_process_node'].getValue() if "VIEWER_INPUT" not in ipn and ip: From 8934fba38883963349d5773de9dd3c7535bd35fc Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 30 Jul 2020 17:45:05 +0200 Subject: [PATCH 216/947] make png and jpeg configurable in config --- pype/plugins/photoshop/publish/extract_image.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pype/plugins/photoshop/publish/extract_image.py b/pype/plugins/photoshop/publish/extract_image.py index 1bb13bce6b..6dfccdc4f2 100644 --- a/pype/plugins/photoshop/publish/extract_image.py +++ b/pype/plugins/photoshop/publish/extract_image.py @@ -13,6 +13,7 @@ class ExtractImage(pype.api.Extractor): label = "Extract Image" hosts = ["photoshop"] families = ["image"] + formats = ["png", "jpg"] def process(self, instance): @@ -32,10 +33,12 @@ class ExtractImage(pype.api.Extractor): if layer.id not in extract_ids: layer.Visible = False - save_options = { - "png": photoshop.com_objects.PNGSaveOptions(), - "jpg": photoshop.com_objects.JPEGSaveOptions() - } + save_options = {} + if "png" in self.formats: + save_options["png"] = photoshop.com_objects.PNGSaveOptions() + if "jpg" in self.formats: + save_options["jpg"] = photoshop.com_objects.JPEGSaveOptions() + file_basename = os.path.splitext( photoshop.app().ActiveDocument.Name )[0] From c59eeab6e60aae9f01bcd399d387259e41646a2d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 31 Jul 2020 15:35:06 +0200 Subject: [PATCH 217/947] some code cleanup --- pype/plugins/global/publish/cleanup.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pype/plugins/global/publish/cleanup.py b/pype/plugins/global/publish/cleanup.py index 3ab41f90ca..bca540078f 100644 --- a/pype/plugins/global/publish/cleanup.py +++ b/pype/plugins/global/publish/cleanup.py @@ -1,11 +1,18 @@ +# -*- coding: utf-8 -*- +"""Cleanup leftover files from publish.""" import os import shutil import pyblish.api def clean_renders(instance): - transfers = instance.data.get("transfers", list()) + """Delete renders after publishing. + Args: + instance (pyblish.api.Instace): Instance to work on. + + """ + transfers = instance.data.get("transfers", list()) current_families = instance.data.get("families", list()) instance_family = instance.data.get("family", None) dirnames = [] @@ -40,6 +47,7 @@ class CleanUp(pyblish.api.InstancePlugin): active = True def process(self, instance): + """Plugin entry point.""" # Get the errored instances failed = [] for result in instance.context.data["results"]: @@ -52,7 +60,7 @@ class CleanUp(pyblish.api.InstancePlugin): ) ) - self.log.info("Cleaning renders ...") + self.log.info("Performing cleanup on {}".format(instance)) clean_renders(instance) if [ef for ef in self.exclude_families @@ -60,16 +68,17 @@ class CleanUp(pyblish.api.InstancePlugin): return import tempfile - staging_dir = instance.data.get("stagingDir", None) - if not staging_dir or not os.path.exists(staging_dir): - self.log.info("No staging directory found: %s" % staging_dir) - return - temp_root = tempfile.gettempdir() + staging_dir = instance.data.get("stagingDir", None) + if not os.path.normpath(staging_dir).startswith(temp_root): self.log.info("Skipping cleanup. Staging directory is not in the " "temp folder: %s" % staging_dir) return - self.log.info("Removing staging directory ...") + if not staging_dir or not os.path.exists(staging_dir): + self.log.info("No staging directory found: %s" % staging_dir) + return + + self.log.info("Removing staging directory {}".format(staging_dir)) shutil.rmtree(staging_dir) From 5b21943ed840e635634339158e532e69a4b3b7d3 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 31 Jul 2020 17:43:57 +0200 Subject: [PATCH 218/947] make decision about ma/mb configurable per family --- ...yaAscii.py => extract_camera_mayaScene.py} | 39 ++++++++++++----- ...ascii_raw.py => extract_maya_scene_raw.py} | 35 +++++++++++----- pype/plugins/maya/publish/extract_model.py | 32 ++++++++++---- pype/plugins/maya/publish/extract_rig.py | 33 +++++++++++---- pype/plugins/maya/publish/extract_yeti_rig.py | 42 +++++++++++++------ 5 files changed, 131 insertions(+), 50 deletions(-) rename pype/plugins/maya/publish/{extract_camera_mayaAscii.py => extract_camera_mayaScene.py} (82%) rename pype/plugins/maya/publish/{extract_maya_ascii_raw.py => extract_maya_scene_raw.py} (60%) diff --git a/pype/plugins/maya/publish/extract_camera_mayaAscii.py b/pype/plugins/maya/publish/extract_camera_mayaScene.py similarity index 82% rename from pype/plugins/maya/publish/extract_camera_mayaAscii.py rename to pype/plugins/maya/publish/extract_camera_mayaScene.py index 973d8d452a..03dde031e9 100644 --- a/pype/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/pype/plugins/maya/publish/extract_camera_mayaScene.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Extract camera as Maya Scene.""" import os from maya import cmds @@ -65,8 +67,8 @@ def unlock(plug): cmds.disconnectAttr(source, destination) -class ExtractCameraMayaAscii(pype.api.Extractor): - """Extract a Camera as Maya Ascii. +class ExtractCameraMayaScene(pype.api.Extractor): + """Extract a Camera as Maya Scene. This will create a duplicate of the camera that will be baked *with* substeps and handles for the required frames. This temporary duplicate @@ -81,13 +83,28 @@ class ExtractCameraMayaAscii(pype.api.Extractor): """ - label = "Camera (Maya Ascii)" + label = "Camera (Maya Scene)" hosts = ["maya"] families = ["camera"] + scene_type = "ma" def process(self, instance): - + """Plugin entry point.""" # get settings + ext_mapping = instance.context.data["presets"]["maya"].get("ext_mapping") # noqa: E501 + if ext_mapping: + self.log.info("Looking in presets for scene type ...") + # use extension mapping for first family found + for family in self.families: + try: + self.scene_type = ext_mapping[family] + self.log.info( + "Using {} as scene type".format(self.scene_type)) + break + except AttributeError: + # no preset found + pass + framerange = [instance.data.get("frameStart", 1), instance.data.get("frameEnd", 1)] handles = instance.data.get("handles", 0) @@ -95,7 +112,7 @@ class ExtractCameraMayaAscii(pype.api.Extractor): bake_to_worldspace = instance.data("bakeToWorldSpace", True) if not bake_to_worldspace: - self.log.warning("Camera (Maya Ascii) export only supports world" + self.log.warning("Camera (Maya Scene) export only supports world" "space baked camera extractions. The disabled " "bake to world space is ignored...") @@ -115,7 +132,7 @@ class ExtractCameraMayaAscii(pype.api.Extractor): # Define extract output file path dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) + filename = "{0}.{1}".format(instance.name, self.scene_type) path = os.path.join(dir_path, filename) # Perform extraction @@ -152,7 +169,7 @@ class ExtractCameraMayaAscii(pype.api.Extractor): cmds.select(baked_shapes, noExpand=True) cmds.file(path, force=True, - typ="mayaAscii", + typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 exportSelected=True, preserveReferences=False, constructionHistory=False, @@ -164,15 +181,15 @@ class ExtractCameraMayaAscii(pype.api.Extractor): # Delete the baked hierarchy if bake_to_worldspace: cmds.delete(baked) - - massage_ma_file(path) + if self.scene_type == "ma": + massage_ma_file(path) if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'ma', - 'ext': 'ma', + 'name': self.scene_type, + 'ext': self.scene_type, 'files': filename, "stagingDir": dir_path, } diff --git a/pype/plugins/maya/publish/extract_maya_ascii_raw.py b/pype/plugins/maya/publish/extract_maya_scene_raw.py similarity index 60% rename from pype/plugins/maya/publish/extract_maya_ascii_raw.py rename to pype/plugins/maya/publish/extract_maya_scene_raw.py index 895b6acbfe..2971572552 100644 --- a/pype/plugins/maya/publish/extract_maya_ascii_raw.py +++ b/pype/plugins/maya/publish/extract_maya_scene_raw.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Extract data as Maya scene (raw).""" import os from maya import cmds @@ -6,24 +8,37 @@ import avalon.maya import pype.api -class ExtractMayaAsciiRaw(pype.api.Extractor): - """Extract as Maya Ascii (raw) +class ExtractMayaSceneRaw(pype.api.Extractor): + """Extract as Maya Scene (raw). This will preserve all references, construction history, etc. - """ - label = "Maya ASCII (Raw)" + label = "Maya Scene (Raw)" hosts = ["maya"] families = ["mayaAscii", "setdress", "layout"] + scene_type = "ma" def process(self, instance): - + """Plugin entry point.""" + ext_mapping = instance.context.data["presets"]["maya"].get("ext_mapping") # noqa: E501 + if ext_mapping: + self.log.info("Looking in presets for scene type ...") + # use extension mapping for first family found + for family in self.families: + try: + self.scene_type = ext_mapping[family] + self.log.info( + "Using {} as scene type".format(self.scene_type)) + break + except AttributeError: + # no preset found + pass # Define extract output file path dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) + filename = "{0}.{1}".format(instance.name, self.scene_type) path = os.path.join(dir_path, filename) # Whether to include all nodes in the instance (including those from @@ -38,12 +53,12 @@ class ExtractMayaAsciiRaw(pype.api.Extractor): members = instance[:] # Perform extraction - self.log.info("Performing extraction..") + self.log.info("Performing extraction ...") with avalon.maya.maintained_selection(): cmds.select(members, noExpand=True) cmds.file(path, force=True, - typ="mayaAscii", + typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 exportSelected=True, preserveReferences=True, constructionHistory=True, @@ -55,8 +70,8 @@ class ExtractMayaAsciiRaw(pype.api.Extractor): instance.data["representations"] = [] representation = { - 'name': 'ma', - 'ext': 'ma', + 'name': self.scene_type, + 'ext': self.scene_type, 'files': filename, "stagingDir": dir_path } diff --git a/pype/plugins/maya/publish/extract_model.py b/pype/plugins/maya/publish/extract_model.py index ba56194eea..330e471e53 100644 --- a/pype/plugins/maya/publish/extract_model.py +++ b/pype/plugins/maya/publish/extract_model.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Extract model as Maya Scene.""" import os from maya import cmds @@ -8,7 +10,7 @@ from pype.hosts.maya import lib class ExtractModel(pype.api.Extractor): - """Extract as Model (Maya Ascii) + """Extract as Model (Maya Scene). Only extracts contents based on the original "setMembers" data to ensure publishing the least amount of required shapes. From that it only takes @@ -22,19 +24,33 @@ class ExtractModel(pype.api.Extractor): """ - label = "Model (Maya ASCII)" + label = "Model (Maya Scene)" hosts = ["maya"] families = ["model"] + scene_type = "ma" def process(self, instance): - + """Plugin entry point.""" + ext_mapping = instance.context.data["presets"]["maya"].get("ext_mapping") # noqa: E501 + if ext_mapping: + self.log.info("Looking in presets for scene type ...") + # use extension mapping for first family found + for family in self.families: + try: + self.scene_type = ext_mapping[family] + self.log.info( + "Using {} as scene type".format(self.scene_type)) + break + except AttributeError: + # no preset found + pass # Define extract output file path stagingdir = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) + filename = "{0}.{1}".format(instance.name, self.scene_type) path = os.path.join(stagingdir, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.info("Performing extraction ...") # Get only the shape contents we need in such a way that we avoid # taking along intermediateObjects @@ -59,7 +75,7 @@ class ExtractModel(pype.api.Extractor): cmds.select(members, noExpand=True) cmds.file(path, force=True, - typ="mayaAscii", + typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 exportSelected=True, preserveReferences=False, channels=False, @@ -73,8 +89,8 @@ class ExtractModel(pype.api.Extractor): instance.data["representations"] = [] representation = { - 'name': 'ma', - 'ext': 'ma', + 'name': self.scene_type, + 'ext': self.scene_type, 'files': filename, "stagingDir": stagingdir, } diff --git a/pype/plugins/maya/publish/extract_rig.py b/pype/plugins/maya/publish/extract_rig.py index c98e562313..8ebeae4184 100644 --- a/pype/plugins/maya/publish/extract_rig.py +++ b/pype/plugins/maya/publish/extract_rig.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Extract rig as Maya Scene.""" import os from maya import cmds @@ -7,26 +9,40 @@ import pype.api class ExtractRig(pype.api.Extractor): - """Extract rig as Maya Ascii""" + """Extract rig as Maya Scene.""" - label = "Extract Rig (Maya ASCII)" + label = "Extract Rig (Maya Scene)" hosts = ["maya"] families = ["rig"] + scene_type = "ma" def process(self, instance): - + """Plugin entry point.""" + ext_mapping = instance.context.data["presets"]["maya"].get("ext_mapping") # noqa: E501 + if ext_mapping: + self.log.info("Looking in presets for scene type ...") + # use extension mapping for first family found + for family in self.families: + try: + self.scene_type = ext_mapping[family] + self.log.info( + "Using {} as scene type".format(self.scene_type)) + break + except AttributeError: + # no preset found + pass # Define extract output file path dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) + filename = "{0}.{1}".format(instance.name, self.scene_type) path = os.path.join(dir_path, filename) # Perform extraction - self.log.info("Performing extraction..") + self.log.info("Performing extraction ...") with avalon.maya.maintained_selection(): cmds.select(instance, noExpand=True) cmds.file(path, force=True, - typ="mayaAscii", + typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 exportSelected=True, preserveReferences=False, channels=True, @@ -38,12 +54,11 @@ class ExtractRig(pype.api.Extractor): instance.data["representations"] = [] representation = { - 'name': 'ma', - 'ext': 'ma', + 'name': self.scene_type, + 'ext': self.scene_type, 'files': filename, "stagingDir": dir_path } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/pype/plugins/maya/publish/extract_yeti_rig.py b/pype/plugins/maya/publish/extract_yeti_rig.py index d7bbcd6555..2f66d3e026 100644 --- a/pype/plugins/maya/publish/extract_yeti_rig.py +++ b/pype/plugins/maya/publish/extract_yeti_rig.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +"""Extract Yeti rig.""" + import os import json import contextlib @@ -11,7 +14,7 @@ import pype.hosts.maya.lib as maya @contextlib.contextmanager def disconnect_plugs(settings, members): - + """Disconnect and store attribute connections.""" members = cmds.ls(members, long=True) original_connections = [] try: @@ -55,7 +58,7 @@ def disconnect_plugs(settings, members): @contextlib.contextmanager def yetigraph_attribute_values(assumed_destination, resources): - + """Get values from Yeti attributes in graph.""" try: for resource in resources: if "graphnode" not in resource: @@ -89,14 +92,28 @@ def yetigraph_attribute_values(assumed_destination, resources): class ExtractYetiRig(pype.api.Extractor): - """Extract the Yeti rig to a MayaAscii and write the Yeti rig data""" + """Extract the Yeti rig to a Maya Scene and write the Yeti rig data.""" label = "Extract Yeti Rig" hosts = ["maya"] families = ["yetiRig"] + scene_type = "ma" def process(self, instance): - + """Plugin entry point.""" + ext_mapping = instance.context.data["presets"]["maya"].get("ext_mapping") # noqa: E501 + if ext_mapping: + self.log.info("Looking in presets for scene type ...") + # use extension mapping for first family found + for family in self.families: + try: + self.scene_type = ext_mapping[family] + self.log.info( + "Using {} as scene type".format(self.scene_type)) + break + except AttributeError: + # no preset found + pass yeti_nodes = cmds.ls(instance, type="pgYetiMaya") if not yeti_nodes: raise RuntimeError("No pgYetiMaya nodes found in the instance") @@ -106,7 +123,8 @@ class ExtractYetiRig(pype.api.Extractor): settings_path = os.path.join(dirname, "yeti.rigsettings") # Yeti related staging dirs - maya_path = os.path.join(dirname, "yeti_rig.ma") + maya_path = os.path.join( + dirname, "yeti_rig.{}".format(self.scene_type)) self.log.info("Writing metadata file") @@ -153,7 +171,7 @@ class ExtractYetiRig(pype.api.Extractor): cmds.file(maya_path, force=True, exportSelected=True, - typ="mayaAscii", + typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501 preserveReferences=False, constructionHistory=True, shader=False) @@ -163,21 +181,21 @@ class ExtractYetiRig(pype.api.Extractor): if "representations" not in instance.data: instance.data["representations"] = [] - self.log.info("rig file: {}".format("yeti_rig.ma")) + self.log.info("rig file: {}".format(maya_path)) instance.data["representations"].append( { - 'name': "ma", - 'ext': 'ma', - 'files': "yeti_rig.ma", + 'name': self.scene_type, + 'ext': self.scene_type, + 'files': os.path.basename(maya_path), 'stagingDir': dirname } ) - self.log.info("settings file: {}".format("yeti.rigsettings")) + self.log.info("settings file: {}".format(settings)) instance.data["representations"].append( { 'name': 'rigsettings', 'ext': 'rigsettings', - 'files': 'yeti.rigsettings', + 'files': os.path.basename(settings), 'stagingDir': dirname } ) From 7394e7284a7ccef06e9c2321088bc0cb7a314b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Sun, 2 Aug 2020 23:08:55 +0200 Subject: [PATCH 219/947] disable undo/redo during extraction, fix frame num --- pype/hosts/harmony/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 3cae695852..d1a9c3ae17 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -151,6 +151,7 @@ def application_launch(): def export_template(backdrops, nodes, filepath): func = """function func(args) { + scene.beginUndoRedoAccum("Publish: export template"); // Add an extra node just so a new group can be created. var temp_node = node.add("Top", "temp_note", "NOTE", 0, 0, 0); var template_group = node.createGroup(temp_node, "temp_group"); @@ -168,7 +169,7 @@ def export_template(backdrops, nodes, filepath): }; // Copy-paste the selected nodes into the new group. - var drag_object = copyPaste.copy(args[1], 1, frame.numberOf, ""); + var drag_object = copyPaste.copy(args[1], 1, frame.numberOf(), ""); copyPaste.pasteNewNodes(drag_object, template_group, ""); // Select all nodes within group and export as template. @@ -179,6 +180,7 @@ def export_template(backdrops, nodes, filepath): // created during the process. Action.perform("onActionUpToParent()", "Node View"); node.deleteNode(template_group, true, true); + scene.cancelUndoRedoAccum(); } func """ From 0f32a6d056a48c5102d07c023ea335f81224b63a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 4 Aug 2020 13:57:41 +0200 Subject: [PATCH 220/947] use Action.perform copy() to copy nodes --- pype/hosts/harmony/__init__.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index d1a9c3ae17..d4b7d91fdb 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -151,28 +151,31 @@ def application_launch(): def export_template(backdrops, nodes, filepath): func = """function func(args) { - scene.beginUndoRedoAccum("Publish: export template"); - // Add an extra node just so a new group can be created. + var temp_node = node.add("Top", "temp_note", "NOTE", 0, 0, 0); var template_group = node.createGroup(temp_node, "temp_group"); node.deleteNode( template_group + "/temp_note" ); - // This will make Node View to focus on the new group. + selection.clearSelection(); + for (var f = 0; f < args[1].length; f++) + { + selection.addNodeToSelection(args[1][f]); + } + + Action.perform("copy()", "Node View"); + selection.clearSelection(); selection.addNodeToSelection(template_group); Action.perform("onActionEnterGroup()", "Node View"); + Action.perform("paste()", "Node View"); // Recreate backdrops in group. for (var i = 0 ; i < args[0].length; i++) { + MessageLog.trace(args[0][i]); Backdrop.addBackdrop(template_group, args[0][i]); }; - // Copy-paste the selected nodes into the new group. - var drag_object = copyPaste.copy(args[1], 1, frame.numberOf(), ""); - copyPaste.pasteNewNodes(drag_object, template_group, ""); - - // Select all nodes within group and export as template. Action.perform( "selectAll()", "Node View" ); copyPaste.createTemplateFromSelection(args[2], args[3]); @@ -180,7 +183,6 @@ def export_template(backdrops, nodes, filepath): // created during the process. Action.perform("onActionUpToParent()", "Node View"); node.deleteNode(template_group, true, true); - scene.cancelUndoRedoAccum(); } func """ From 9a4c7673e9244f8486655f2c7795f6f5b67553ae Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 4 Aug 2020 21:37:35 +0200 Subject: [PATCH 221/947] temporary change to extract render logic collection should eventually be phased out in favour of explicit render collection --- .../plugins/harmony/publish/extract_render.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index fe1352f9f9..45b52e0307 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -72,19 +72,27 @@ class ExtractRender(pyblish.api.InstancePlugin): self.log.info(output.decode("utf-8")) # Collect rendered files. + self.log.debug(path) files = os.listdir(path) + self.log.debug(files) collections, remainder = clique.assemble(files, minimum_items=1) assert not remainder, ( "There should not be a remainder for {0}: {1}".format( instance[0], remainder ) ) - assert len(collections) == 1, ( - "There should only be one image sequence in {}. Found: {}".format( - path, len(collections) - ) - ) - collection = collections[0] + self.log.debug(collections) + if len(collections) > 1: + for col in collections: + if len(list(col)) > 1: + collection = col + else: + # assert len(collections) == 1, ( + # "There should only be one image sequence in {}. Found: {}".format( + # path, len(collections) + # ) + # ) + collection = collections[0] # Generate thumbnail. thumbnail_path = os.path.join(path, "thumbnail.png") From 0c883a9f5b300874be3ba52849936e17ac09673a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 4 Aug 2020 21:38:29 +0200 Subject: [PATCH 222/947] temp frame range filter implementation temporary implementation for a client. must be cleaned up and logic should be changed to work based on task type --- .../harmony/publish/validate_scene_settings.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index aa9a70bd85..3602f1ca22 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -28,8 +28,11 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): hosts = ["harmony"] actions = [ValidateSceneSettingsRepair] + frame_check_filter = ["_ch_", "_pr_", "_intd_", "_extd_"] + def process(self, instance): expected_settings = pype.hosts.harmony.get_asset_settings() + self.log.info(expected_settings) # Harmony is expected to start at 1. frame_start = expected_settings["frameStart"] @@ -37,6 +40,14 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): expected_settings["frameEnd"] = frame_end - frame_start + 1 expected_settings["frameStart"] = 1 + + + self.log.info(instance.context.data['anatomyData']['asset']) + + if any(string in instance.context.data['anatomyData']['asset'] + for string in frame_check_filter): + expected_settings.pop("frameEnd") + func = """function func() { return { From 5205e1773e4eb178baee3f6f000d17886a4a908c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 4 Aug 2020 21:42:02 +0200 Subject: [PATCH 223/947] bump version --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index 200c236308..ddcf716b76 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.11.1" +__version__ = "2.11.3" From 123fb5ff861fb5f344f5be8fe37b23c786f2d268 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 31 Jul 2020 18:50:05 +0200 Subject: [PATCH 224/947] feat(standalone): editorial wip --- .../publish/collect_clips.py | 230 +++++++++++ .../publish/collect_editorial.py | 76 ++++ .../publish/collect_frame_ranges.py | 56 +++ .../publish/collect_hierarchy.py | 358 ++++++++++++++++++ .../publish/collect_shot_names.py | 15 + .../publish/collect_shots.py | 147 ------- .../publish/extract_shot.py | 129 ++++--- .../publish/validate_clips.py | 24 ++ .../publish/validate_editorial_resources.py | 8 +- ...e_shots.py => validate_shot_duplicates.py} | 6 +- 10 files changed, 836 insertions(+), 213 deletions(-) create mode 100644 pype/plugins/standalonepublisher/publish/collect_clips.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_editorial.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_frame_ranges.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_hierarchy.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_shot_names.py delete mode 100644 pype/plugins/standalonepublisher/publish/collect_shots.py create mode 100644 pype/plugins/standalonepublisher/publish/validate_clips.py rename pype/plugins/standalonepublisher/publish/{validate_shots.py => validate_shot_duplicates.py} (77%) diff --git a/pype/plugins/standalonepublisher/publish/collect_clips.py b/pype/plugins/standalonepublisher/publish/collect_clips.py new file mode 100644 index 0000000000..e38a70f289 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_clips.py @@ -0,0 +1,230 @@ +import os + +import opentimelineio as otio +from bson import json_util + +import pyblish.api +from pype import lib as plib +from avalon import io + + +class CollectClips(pyblish.api.InstancePlugin): + """Collect Clips instances from editorial's OTIO sequence""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Collect Shots" + hosts = ["standalonepublisher"] + families = ["editorial"] + + def process(self, instance): + # get context + context = instance.context + + # create asset_names conversion table + if not context.data.get("assetsShared"): + self.log.debug("Created `assetsShared` in context") + context.data["assetsShared"] = dict() + + # get timeline otio data + timeline = instance.data["otio_timeline"] + fps = plib.get_asset()["data"]["fps"] + + tracks = timeline.each_child( + descended_from_type=otio.schema.track.Track + ) + self.log.debug(f"__ tracks: `{tracks}`") + + # get data from avalon + asset_entity = instance.context.data["assetEntity"] + asset_data = asset_entity["data"] + asset_name = asset_entity["name"] + self.log.debug(f"__ asset_entity: `{asset_entity}`") + + # Project specific prefix naming. This needs to be replaced with some + # options to be more flexible. + asset_name = asset_name.split("_")[0] + + instances = [] + for track in tracks: + self.log.debug(f"__ track: `{track}`") + try: + track_start_frame = ( + abs(track.source_range.start_time.value) + ) + except AttributeError: + track_start_frame = 0 + + self.log.debug(f"__ track: `{track}`") + + for clip in track.each_child(): + # skip all generators like black ampty + if isinstance( + clip.media_reference, + otio.schema.GeneratorReference): + continue + + # Transitions are ignored, because Clips have the full frame + # range. + if isinstance(clip, otio.schema.transition.Transition): + continue + + if clip.name is None: + continue + + # Hardcoded to expect a shot name of "[name].[extension]" + clip_name = os.path.splitext(clip.name)[0].lower() + name = f"{asset_name}_{clip_name}" + + source_in = clip.trimmed_range().start_time.value + clip_in = clip.range_in_parent().start_time.value + clip_out = clip.range_in_parent().end_time_inclusive().value + clip_duration = clip.duration().value + self.log.debug(f"__ source_in: `{source_in}`") + self.log.debug(f"__ clip_in: `{clip_in}`") + self.log.debug(f"__ clip_out: `{clip_out}`") + self.log.debug(f"__ clip_duration: `{clip_duration}`") + + label = f"{name} (framerange: {clip_in}-{clip_out})" + + instances.append( + instance.context.create_instance(**{ + "name": name, + "label": label, + "asset": name, + "subset": "plateRef", + "item": clip, + + # timing properities + "trackStartFrame": track_start_frame, + "sourceIn": source_in, + "sourceOut": source_in + clip_duration, + "clipIn": clip_in, + "clipOut": clip_out, + "clipDuration": clip_duration, + "handleStart": asset_data["handleStart"], + "handleEnd": asset_data["handleEnd"], + "fps": fps, + + # instance properities + "family": "clip", + "families": ["review", "ftrack"], + "ftrackFamily": "review", + "representations": [] + }) + ) + # + # def process_old(self, instance): + # representation = instance.data["representations"][0] + # file_path = os.path.join( + # representation["stagingDir"], representation["files"] + # ) + # instance.context.data["editorialPath"] = file_path + # + # extension = os.path.splitext(file_path)[1][1:] + # kwargs = {} + # if extension == "edl": + # # EDL has no frame rate embedded so needs explicit frame rate else + # # 24 is asssumed. + # kwargs["rate"] = plib.get_asset()["data"]["fps"] + # + # timeline = otio.adapters.read_from_file(file_path, **kwargs) + # tracks = timeline.each_child( + # descended_from_type=otio.schema.track.Track + # ) + # asset_entity = instance.context.data["assetEntity"] + # asset_name = asset_entity["name"] + # + # # Ask user for sequence start. Usually 10:00:00:00. + # sequence_start_frame = 900000 + # + # # Project specific prefix naming. This needs to be replaced with some + # # options to be more flexible. + # asset_name = asset_name.split("_")[0] + # + # instances = [] + # for track in tracks: + # track_start_frame = ( + # abs(track.source_range.start_time.value) - sequence_start_frame + # ) + # for child in track.each_child(): + # # skip all generators like black ampty + # if isinstance( + # child.media_reference, + # otio.schema.GeneratorReference): + # continue + # + # # Transitions are ignored, because Clips have the full frame + # # range. + # if isinstance(child, otio.schema.transition.Transition): + # continue + # + # if child.name is None: + # continue + # + # # Hardcoded to expect a shot name of "[name].[extension]" + # child_name = os.path.splitext(child.name)[0].lower() + # name = f"{asset_name}_{child_name}" + # + # frame_start = track_start_frame + # frame_start += child.range_in_parent().start_time.value + # frame_end = track_start_frame + # frame_end += child.range_in_parent().end_time_inclusive().value + # + # label = f"{name} (framerange: {frame_start}-{frame_end})" + # instances.append( + # instance.context.create_instance(**{ + # "name": name, + # "label": label, + # "frameStart": frame_start, + # "frameEnd": frame_end, + # "family": "shot", + # "families": ["review", "ftrack"], + # "ftrackFamily": "review", + # "asset": name, + # "subset": "shotMain", + # "representations": [], + # "source": file_path + # }) + # ) + # + # visual_hierarchy = [asset_entity] + # while True: + # visual_parent = io.find_one( + # {"_id": visual_hierarchy[-1]["data"]["visualParent"]} + # ) + # if visual_parent: + # visual_hierarchy.append(visual_parent) + # else: + # visual_hierarchy.append(instance.context.data["projectEntity"]) + # break + # + # context_hierarchy = None + # for entity in visual_hierarchy: + # childs = {} + # if context_hierarchy: + # name = context_hierarchy.pop("name") + # childs = {name: context_hierarchy} + # else: + # for instance in instances: + # childs[instance.data["name"]] = { + # "childs": {}, + # "entity_type": "Shot", + # "custom_attributes": { + # "frameStart": instance.data["frameStart"], + # "frameEnd": instance.data["frameEnd"] + # } + # } + # + # context_hierarchy = { + # "entity_type": entity["data"]["entityType"], + # "childs": childs, + # "name": entity["name"] + # } + # + # name = context_hierarchy.pop("name") + # context_hierarchy = {name: context_hierarchy} + # instance.context.data["hierarchyContext"] = context_hierarchy + # self.log.info( + # "Hierarchy:\n" + + # json_util.dumps(context_hierarchy, sort_keys=True, indent=4) + # ) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py new file mode 100644 index 0000000000..52acead6cc --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -0,0 +1,76 @@ +import os +import opentimelineio as otio +import pyblish.api +from pype import lib as plib +import pype.api + +class OTIO_View(pyblish.api.Action): + """Currently disabled because OTIO requires PySide2. Issue on Qt.py: + https://github.com/PixarAnimationStudios/OpenTimelineIO/issues/289 + """ + + label = "OTIO View" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + instance = context[0] + representation = instance.data["representations"][0] + file_path = os.path.join( + representation["stagingDir"], representation["files"] + ) + plib._subprocess(["otioview", file_path]) + + +class CollectEditorial(pyblish.api.InstancePlugin): + """Collect Editorial OTIO timeline""" + + order = pyblish.api.CollectorOrder + label = "Collect Editorial" + hosts = ["standalonepublisher"] + families = ["editorial"] + actions = [] + + # presets + extensions = [".mov", ".mp4"] + + def process(self, instance): + self.log.debug(f"__ instance: `{instance}`") + # get representation with editorial file + representation = instance.data["representations"][0] + + # make editorial sequence file path + staging_dir = representation["stagingDir"] + file_path = os.path.join( + staging_dir, representation["files"] + ) + + # get video file path + video_path = None + basename = os.path.splitext(os.path.basename(file_path))[0] + for f in os.listdir(staging_dir): + self.log.debug(f"__ test file: `{f}`") + # filter out by not sharing the same name + if os.path.splitext(f)[0] not in basename: + continue + # filter out by respected extensions + if os.path.splitext(f)[1] not in self.extensions: + continue + video_path = os.path.join( + staging_dir, f + ) + self.log.debug(f"__ video_path: `{video_path}`") + instance.context.data["editorialVideoPath"] = video_path + + # get editorial sequence file into otio timeline object + extension = os.path.splitext(file_path)[1] + kwargs = {} + if extension == ".edl": + # EDL has no frame rate embedded so needs explicit frame rate else + # 24 is asssumed. + kwargs["rate"] = plib.get_asset()["data"]["fps"] + + instance.data["otio_timeline"] = otio.adapters.read_from_file( + file_path, **kwargs) + + self.log.info(f"Added OTIO timeline from: `{file_path}`") diff --git a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py new file mode 100644 index 0000000000..5e8292458f --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py @@ -0,0 +1,56 @@ +import pyblish.api + + +class CollectClipFrameRanges(pyblish.api.InstancePlugin): + """Collect all frame range data""" + + order = pyblish.api.CollectorOrder + 0.101 + label = "Collect Frame Ranges" + hosts = ["standalonepublisher"] + families = ["clip"] + + # presets + start_frame_offset = None # if 900000 for edl default then -900000 + custom_start_frame = None + + def process(self, instance): + + data = dict() + + # Timeline data. + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + + source_in_h = instance.data("sourceInH", + instance.data("sourceIn") - handle_start) + source_out_h = instance.data("sourceOutH", + instance.data("sourceOut") + handle_end) + + timeline_in = instance.data["clipIn"] + timeline_out = instance.data["clipOut"] + + timeline_in_h = timeline_in - handle_start + timeline_out_h = timeline_out + handle_end + + # define starting frame for future shot + frame_start = self.custom_start_frame or timeline_in + + # add offset in case there is any + if self.start_frame_offset: + frame_start += self.start_frame_offset + + frame_end = frame_start + (timeline_out - timeline_in) + + data.update({ + "sourceInH": source_in_h, + "sourceOutH": source_out_h, + "frameStart": frame_start, + "frameEnd": frame_end, + "clipInH": timeline_in_h, + "clipOutH": timeline_out_h, + "clipDurationH": instance.data.get( + "clipDuration") + handle_start + handle_end + } + ) + self.log.debug("__ data: {}".format(data)) + instance.data.update(data) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py new file mode 100644 index 0000000000..3a49b499da --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -0,0 +1,358 @@ +import pyblish.api +import avalon.api as avalon +import re +import os + + +class CollectHierarchyInstance(pyblish.api.InstancePlugin): + """Collecting hierarchy context from `parents` and `hierarchy` data + present in `clip` family instances coming from the request json data file + + It will add `hierarchical_context` into each instance for integrate + plugins to be able to create needed parents for the context if they + don't exist yet + """ + + label = "Collect Hierarchy Clip" + order = pyblish.api.CollectorOrder + 0.101 + hosts = ["standalonepublisher"] + families = ["clip"] + + # presets + search_patterns = { + "sequence": r"sc\d{3}", + "shot": r"sh\d{3}", + "episode": r"ep\d{2}" + } + shot_name_template = "{project[code]}{episode}{clip_name}" + shot_hierarchy = "{episode}{sequence}/{clip_name}" + shot_tasks = ["Animation", "Layout"] + + def convert_to_entity(self, key, value): + # ftrack compatible entity types + types = {"shot": "Shot", + "folder": "Folder", + "episode": "Episode", + "sequence": "Sequence", + "track": "Sequence", + } + # convert to entity type + entity_type = types.get(key, None) + + # return if any + if entity_type: + return {"entityType": entity_type, "entityName": value} + + def process(self, instance): + search_text = "" + context = instance.context + anatomy_data = context.data["anatomyData"] + asset_entity = context.data["assetEntity"] + asset_name = asset_entity["name"] + assets_shared = context.data.get("assetsShared") + + clip = instance.data["item"] + clip_name = os.path.splitext(clip.name)[0].lower() + asset = instance.data["asset"] + + clip_in = instance.data["clipIn"] + clip_out = instance.data["clipOut"] + fps = instance.data["fps"] + + hierarchy_data = dict(anatomy_data) + if self.search_patterns: + search_text += clip_name + asset_name + hierarchy_data.update({"clip_name": clip_name}) + for type, pattern in self.search_patterns.items(): + p = re.compile(pattern) + match = p.findall(search_text) + if not match: + continue + hierarchy_data[type] = match[-1] + + self.log.debug("__ hierarchy_data: {}".format(hierarchy_data)) + shot_name = self.shot_name_template.format(**hierarchy_data) + self.log.debug("__ shot_name: {}".format(shot_name)) + shot_hierarchy = self.shot_hierarchy.format(**hierarchy_data) + self.log.debug("__ shot_hierarchy: {}".format(shot_hierarchy)) + + # # build data for inner nukestudio project property + # data = { + # "sequence": ( + # context.data['activeSequence'].name().replace(' ', '_') + # ), + # "track": clip.parent().name().replace(' ', '_'), + # "clip": asset + # } + # self.log.debug("__ data: {}".format(data)) + # + # # Check for clips with the same range + # # this is for testing if any vertically neighbouring + # # clips has been already processed + # match = next(( + # k for k, v in assets_shared.items() + # if (v["_clipIn"] == clip_in) + # and (v["_clipOut"] == clip_out) + # ), False) + # + # self.log.debug( + # "__ assets_shared[match]: {}".format( + # assets_shared[match])) + # + # # check if hierarchy key is present in matched + # # vertically neighbouring clip + # if not assets_shared[match].get("hierarchy"): + # match = False + # + # # rise exception if multiple hierarchy tag found + # assert not match, ( + # "Two clips above each other with" + # " hierarchy tag are not allowed" + # " >> keep hierarchy tag only in one of them <<" + # ) + # + # d_metadata = dict() + # parents = list() + # + # # main template from Tag.note + # template = t_note + # + # # if shot in template then remove it + # if "shot" in template.lower(): + # instance.data["asset"] = [ + # t for t in template.split('/')][-1] + # template = "/".join( + # [t for t in template.split('/')][0:-1]) + # + # # take template from Tag.note and break it into parts + # template_split = template.split("/") + # patern = re.compile(r"\{([a-z]*?)\}") + # par_split = [patern.findall(t) + # for t in template.split("/")] + # + # # format all {} in two layers + # for k, v in t_metadata.items(): + # new_k = k.split(".")[1] + # + # # ignore all help strings + # if 'help' in k: + # continue + # # self.log.info("__ new_k: `{}`".format(new_k)) + # try: + # # first try all data and context data to + # # add to individual properties + # new_v = str(v).format( + # **dict(context.data, **data)) + # d_metadata[new_k] = new_v + # + # # create parents + # # find matching index of order + # p_match_i = [i for i, p in enumerate(par_split) + # if new_k in p] + # + # # if any is matching then convert to entity_types + # if p_match_i: + # parent = self.convert_to_entity( + # new_k, template_split[p_match_i[0]]) + # parents.insert(p_match_i[0], parent) + # except Exception: + # d_metadata[new_k] = v + # + # # create new shot asset name + # instance.data["asset"] = instance.data["asset"].format( + # **d_metadata) + # self.log.debug( + # "__ instance.data[asset]: " + # "{}".format(instance.data["asset"]) + # ) + # + # # lastly fill those individual properties itno + # # format the string with collected data + # parents = [{"entityName": p["entityName"].format( + # **d_metadata), "entityType": p["entityType"]} + # for p in parents] + # self.log.debug("__ parents: {}".format(parents)) + # + # hierarchy = template.format( + # **d_metadata) + # self.log.debug("__ hierarchy: {}".format(hierarchy)) + # + # # check if hierarchy attribute is already created + # # it should not be so return warning if it is + # hd = instance.data.get("hierarchy") + # assert not hd, ( + # "Only one Hierarchy Tag is allowed. " + # "Clip: `{}`".format(asset) + # ) + # + # # add formated hierarchy path into instance data + # instance.data["hierarchy"] = hierarchy + # instance.data["parents"] = parents + # + # self.log.info( + # "clip: {asset}[{clip_in}:{clip_out}]".format( + # **locals())) + # # adding to asset shared dict + # self.log.debug( + # "__ assets_shared: {}".format(assets_shared)) + # if assets_shared.get(asset): + # self.log.debug("Adding to shared assets: `{}`".format( + # asset)) + # asset_shared = assets_shared.get(asset) + # else: + # asset_shared = assets_shared[asset] + # + # asset_shared.update({ + # "asset": asset, + # "hierarchy": hierarchy, + # "parents": parents, + # "fps": fps, + # "tasks": instance.data["tasks"] + # }) + # + # # adding frame start if any on instance + # start_frame = instance.data.get("startingFrame") + # if start_frame: + # asset_shared.update({ + # "startingFrame": start_frame + # }) + # self.log.debug( + # "assets_shared: {assets_shared}".format(**locals())) + + +class CollectHierarchyContext(pyblish.api.ContextPlugin): + '''Collecting Hierarchy from instaces and building + context hierarchy tree + ''' + + label = "Collect Hierarchy Context" + order = pyblish.api.CollectorOrder + 0.102 + hosts = ["standalonepublisher"] + + def update_dict(self, ex_dict, new_dict): + for key in ex_dict: + if key in new_dict and isinstance(ex_dict[key], dict): + new_dict[key] = self.update_dict(ex_dict[key], new_dict[key]) + else: + if ex_dict.get(key) and new_dict.get(key): + continue + else: + new_dict[key] = ex_dict[key] + + return new_dict + + def process(self, context): + instances = context[:] + + # create hierarchyContext attr if context has none + + temp_context = {} + for instance in instances: + if 'projectfile' in instance.data.get('family', ''): + continue + + name = instance.data["asset"] + + # get handles + handle_start = int(instance.data["handleStart"]) + handle_end = int(instance.data["handleEnd"]) + + # inject assetsShared to other plates types + assets_shared = context.data.get("assetsShared") + + if assets_shared: + s_asset_data = assets_shared.get(name) + if s_asset_data: + self.log.debug("__ s_asset_data: {}".format(s_asset_data)) + name = instance.data["asset"] = s_asset_data["asset"] + instance.data["parents"] = s_asset_data["parents"] + instance.data["hierarchy"] = s_asset_data["hierarchy"] + instance.data["tasks"] = s_asset_data["tasks"] + instance.data["resolutionWidth"] = s_asset_data[ + "resolutionWidth"] + instance.data["resolutionHeight"] = s_asset_data[ + "resolutionHeight"] + instance.data["pixelAspect"] = s_asset_data["pixelAspect"] + instance.data["fps"] = s_asset_data["fps"] + + # adding frame start if any on instance + start_frame = s_asset_data.get("startingFrame") + if start_frame: + instance.data["frameStart"] = start_frame + instance.data["frameEnd"] = start_frame + ( + instance.data["clipOut"] - + instance.data["clipIn"]) + + + + self.log.debug( + "__ instance.data[parents]: {}".format( + instance.data["parents"] + ) + ) + self.log.debug( + "__ instance.data[hierarchy]: {}".format( + instance.data["hierarchy"] + ) + ) + self.log.debug( + "__ instance.data[name]: {}".format(instance.data["name"]) + ) + + in_info = {} + + in_info["inputs"] = [ + x["_id"] for x in instance.data.get("assetbuilds", []) + ] + + # suppose that all instances are Shots + in_info['entity_type'] = 'Shot' + + # get custom attributes of the shot + if instance.data.get("main"): + in_info['custom_attributes'] = { + "handleStart": handle_start, + "handleEnd": handle_end, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "clipIn": instance.data["clipIn"], + "clipOut": instance.data["clipOut"], + 'fps': instance.context.data["fps"] + } + + # adding SourceResolution if Tag was present + if instance.data.get("main"): + in_info['custom_attributes'].update({ + "resolutionWidth": instance.data["resolutionWidth"], + "resolutionHeight": instance.data["resolutionHeight"], + "pixelAspect": instance.data["pixelAspect"] + }) + + in_info['tasks'] = instance.data['tasks'] + + parents = instance.data.get('parents', []) + self.log.debug("__ in_info: {}".format(in_info)) + + actual = {name: in_info} + + for parent in reversed(parents): + next_dict = {} + parent_name = parent["entityName"] + next_dict[parent_name] = {} + next_dict[parent_name]["entity_type"] = parent["entityType"] + next_dict[parent_name]["childs"] = actual + actual = next_dict + + temp_context = self.update_dict(temp_context, actual) + + # TODO: 100% sure way of get project! Will be Name or Code? + project_name = avalon.Session["AVALON_PROJECT"] + final_context = {} + final_context[project_name] = {} + final_context[project_name]['entity_type'] = 'Project' + final_context[project_name]['childs'] = temp_context + + # adding hierarchy context to instance + context.data["hierarchyContext"] = final_context + self.log.debug("context.data[hierarchyContext] is: {}".format( + context.data["hierarchyContext"])) diff --git a/pype/plugins/standalonepublisher/publish/collect_shot_names.py b/pype/plugins/standalonepublisher/publish/collect_shot_names.py new file mode 100644 index 0000000000..7976c855d3 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_shot_names.py @@ -0,0 +1,15 @@ +import pyblish.api +import re + + +class CollectShotNames(pyblish.api.InstancePlugin): + """ + Collecting shot names + """ + + label = "Collect shot names" + order = pyblish.api.CollectorOrder + 0.01 + hosts = ["standalonepublisher"] + + def process(self, instance): + self.log.info("Instance name: `{}`".format(instance.data["name"])) diff --git a/pype/plugins/standalonepublisher/publish/collect_shots.py b/pype/plugins/standalonepublisher/publish/collect_shots.py deleted file mode 100644 index 4f682bd808..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_shots.py +++ /dev/null @@ -1,147 +0,0 @@ -import os - -import opentimelineio as otio -from bson import json_util - -import pyblish.api -from pype import lib -from avalon import io - - -class OTIO_View(pyblish.api.Action): - """Currently disabled because OTIO requires PySide2. Issue on Qt.py: - https://github.com/PixarAnimationStudios/OpenTimelineIO/issues/289 - """ - - label = "OTIO View" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - instance = context[0] - representation = instance.data["representations"][0] - file_path = os.path.join( - representation["stagingDir"], representation["files"] - ) - lib._subprocess(["otioview", file_path]) - - -class CollectShots(pyblish.api.InstancePlugin): - """Collect Anatomy object into Context""" - - order = pyblish.api.CollectorOrder - label = "Collect Shots" - hosts = ["standalonepublisher"] - families = ["editorial"] - actions = [] - - def process(self, instance): - representation = instance.data["representations"][0] - file_path = os.path.join( - representation["stagingDir"], representation["files"] - ) - instance.context.data["editorialPath"] = file_path - - extension = os.path.splitext(file_path)[1][1:] - kwargs = {} - if extension == "edl": - # EDL has no frame rate embedded so needs explicit frame rate else - # 24 is asssumed. - kwargs["rate"] = lib.get_asset()["data"]["fps"] - - timeline = otio.adapters.read_from_file(file_path, **kwargs) - tracks = timeline.each_child( - descended_from_type=otio.schema.track.Track - ) - asset_entity = instance.context.data["assetEntity"] - asset_name = asset_entity["name"] - - # Ask user for sequence start. Usually 10:00:00:00. - sequence_start_frame = 900000 - - # Project specific prefix naming. This needs to be replaced with some - # options to be more flexible. - asset_name = asset_name.split("_")[0] - - instances = [] - for track in tracks: - track_start_frame = ( - abs(track.source_range.start_time.value) - sequence_start_frame - ) - for child in track.each_child(): - - # Transitions are ignored, because Clips have the full frame - # range. - if isinstance(child, otio.schema.transition.Transition): - continue - - if child.name is None: - continue - - # Hardcoded to expect a shot name of "[name].[extension]" - child_name = os.path.splitext(child.name)[0].lower() - name = f"{asset_name}_{child_name}" - - frame_start = track_start_frame - frame_start += child.range_in_parent().start_time.value - frame_end = track_start_frame - frame_end += child.range_in_parent().end_time_inclusive().value - - label = f"{name} (framerange: {frame_start}-{frame_end})" - instances.append( - instance.context.create_instance(**{ - "name": name, - "label": label, - "frameStart": frame_start, - "frameEnd": frame_end, - "family": "shot", - "families": ["review", "ftrack"], - "ftrackFamily": "review", - "asset": name, - "subset": "shotMain", - "representations": [], - "source": file_path - }) - ) - - visual_hierarchy = [asset_entity] - while True: - visual_parent = io.find_one( - {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - ) - if visual_parent: - visual_hierarchy.append(visual_parent) - else: - visual_hierarchy.append(instance.context.data["projectEntity"]) - break - - context_hierarchy = None - for entity in visual_hierarchy: - childs = {} - if context_hierarchy: - name = context_hierarchy.pop("name") - childs = {name: context_hierarchy} - else: - for instance in instances: - childs[instance.data["name"]] = { - "childs": {}, - "entity_type": "Shot", - "custom_attributes": { - "frameStart": instance.data["frameStart"], - "frameEnd": instance.data["frameEnd"] - } - } - - context_hierarchy = { - "entity_type": entity["data"]["entityType"], - "childs": childs, - "name": entity["name"] - } - - name = context_hierarchy.pop("name") - context_hierarchy = {name: context_hierarchy} - instance.context.data["hierarchyContext"] = context_hierarchy - self.log.info( - "Hierarchy:\n" + - json_util.dumps(context_hierarchy, sort_keys=True, indent=4) - ) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot.py b/pype/plugins/standalonepublisher/publish/extract_shot.py index d58ddfe8d5..e1e69d993b 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot.py @@ -1,9 +1,7 @@ import os - import clique - import pype.api -import pype.lib +import pype.lib as plib class ExtractShot(pype.api.Extractor): @@ -11,42 +9,52 @@ class ExtractShot(pype.api.Extractor): label = "Extract Shot" hosts = ["standalonepublisher"] - families = ["shot"] + families = ["clip"] def process(self, instance): - staging_dir = self.staging_dir(instance) - self.log.info("Outputting shot to {}".format(staging_dir)) + # get context + context = instance.context - editorial_path = instance.context.data["editorialPath"] - basename = os.path.splitext(os.path.basename(editorial_path))[0] + # get ffmpet path + ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + + # get staging dir + staging_dir = self.staging_dir(instance) + self.log.info("Staging dir set to: `{}`".format(staging_dir)) # Generate mov file. - fps = pype.lib.get_asset()["data"]["fps"] - input_path = os.path.join( - os.path.dirname(editorial_path), basename + ".mov" - ) - shot_mov = os.path.join(staging_dir, instance.data["name"] + ".mov") - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + fps = instance.data["fps"] + video_file_path = context.data["editorialVideoPath"] + ext = os.path.splitext(os.path.basename(video_file_path))[-1] + + clip_trimed_path = os.path.join( + staging_dir, instance.data["name"] + ext) + + # check video file metadata + input_data = plib.ffprobe_streams(video_file_path)[0] + self.log.debug(f"__ input_data: `{input_data}`") + args = [ ffmpeg_path, - "-ss", str(instance.data["frameStart"] / fps), - "-i", input_path, + "-ss", str(instance.data["clipIn"] / fps), + "-i", video_file_path, "-t", str( - (instance.data["frameEnd"] - instance.data["frameStart"] + 1) / + (instance.data["clipOut"] - instance.data["clipIn"] + 1) / fps ), "-crf", "18", "-pix_fmt", "yuv420p", - shot_mov + clip_trimed_path ] self.log.info(f"Processing: {args}") - output = pype.lib._subprocess(args) + ffmpeg_args = " ".join(args) + output = pype.api.subprocess(ffmpeg_args) self.log.info(output) instance.data["representations"].append({ - "name": "mov", - "ext": "mov", - "files": os.path.basename(shot_mov), + "name": ext[1:], + "ext": ext[1:], + "files": os.path.basename(clip_trimed_path), "stagingDir": staging_dir, "frameStart": instance.data["frameStart"], "frameEnd": instance.data["frameEnd"], @@ -55,42 +63,41 @@ class ExtractShot(pype.api.Extractor): "tags": ["review", "ftrackreview"] }) - # Generate jpegs. - shot_jpegs = os.path.join( - staging_dir, instance.data["name"] + ".%04d.jpeg" - ) - args = [ffmpeg_path, "-i", shot_mov, shot_jpegs] - self.log.info(f"Processing: {args}") - output = pype.lib._subprocess(args) - self.log.info(output) - - collection = clique.Collection( - head=instance.data["name"] + ".", tail='.jpeg', padding=4 - ) - for f in os.listdir(staging_dir): - if collection.match(f): - collection.add(f) - - instance.data["representations"].append({ - "name": "jpeg", - "ext": "jpeg", - "files": list(collection), - "stagingDir": staging_dir - }) - - # Generate wav file. - shot_wav = os.path.join(staging_dir, instance.data["name"] + ".wav") - args = [ffmpeg_path, "-i", shot_mov, shot_wav] - self.log.info(f"Processing: {args}") - output = pype.lib._subprocess(args) - self.log.info(output) - - instance.data["representations"].append({ - "name": "wav", - "ext": "wav", - "files": os.path.basename(shot_wav), - "stagingDir": staging_dir - }) - - # Required for extract_review plugin (L222 onwards). - instance.data["fps"] = fps + # # Generate jpegs. + # clip_thumbnail = os.path.join( + # staging_dir, instance.data["name"] + ".%04d.jpeg" + # ) + # args = [ffmpeg_path, "-i", clip_trimed_path, clip_thumbnail] + # self.log.info(f"Processing: {args}") + # output = pype.lib._subprocess(args) + # self.log.info(output) + # + # # collect jpeg sequence if editorial data for publish + # # are image sequence + # collection = clique.Collection( + # head=instance.data["name"] + ".", tail='.jpeg', padding=4 + # ) + # for f in os.listdir(staging_dir): + # if collection.match(f): + # collection.add(f) + # + # instance.data["representations"].append({ + # "name": "jpeg", + # "ext": "jpeg", + # "files": list(collection), + # "stagingDir": staging_dir + # }) + # + # # Generate wav file. + # shot_wav = os.path.join(staging_dir, instance.data["name"] + ".wav") + # args = [ffmpeg_path, "-i", clip_trimed_path, shot_wav] + # self.log.info(f"Processing: {args}") + # output = pype.lib._subprocess(args) + # self.log.info(output) + # + # instance.data["representations"].append({ + # "name": "wav", + # "ext": "wav", + # "files": os.path.basename(shot_wav), + # "stagingDir": staging_dir + # }) diff --git a/pype/plugins/standalonepublisher/publish/validate_clips.py b/pype/plugins/standalonepublisher/publish/validate_clips.py new file mode 100644 index 0000000000..35b81da5c1 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/validate_clips.py @@ -0,0 +1,24 @@ + # Check for clips with the same range + # this is for testing if any vertically neighbouring + # clips has been already processed + clip_matching_with_range = next( + (k for k, v in context.data["assetsShared"].items() + if (v.get("_clipIn", 0) == clip_in) + and (v.get("_clipOut", 0) == clip_out) + ), False) + + # check if clip name is the same in matched + # vertically neighbouring clip + # if it is then it is correct and resent variable to False + # not to be rised wrong name exception + if asset in str(clip_matching_with_range): + clip_matching_with_range = False + + # rise wrong name exception if found one + assert (not clip_matching_with_range), ( + "matching clip: {asset}" + " timeline range ({clip_in}:{clip_out})" + " conflicting with {clip_matching_with_range}" + " >> rename any of clips to be the same as the other <<" + ).format( + **locals()) diff --git a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py index 961641b8fa..e4d2279317 100644 --- a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py +++ b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py @@ -12,7 +12,11 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): families = ["editorial"] order = pype.api.ValidateContentsOrder + # presets + check_ext = None + def process(self, instance): + check_ext = self.check_ext or "mov" representation = instance.data["representations"][0] staging_dir = representation["stagingDir"] basename = os.path.splitext( @@ -21,8 +25,8 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): files = [x for x in os.listdir(staging_dir)] - # Check for "mov" file. - filename = basename + ".mov" + # Check for correct extansion in file name. + filename = basename + check_ext filepath = os.path.join(staging_dir, filename) msg = f"Missing \"{filepath}\"." assert filename in files, msg diff --git a/pype/plugins/standalonepublisher/publish/validate_shots.py b/pype/plugins/standalonepublisher/publish/validate_shot_duplicates.py similarity index 77% rename from pype/plugins/standalonepublisher/publish/validate_shots.py rename to pype/plugins/standalonepublisher/publish/validate_shot_duplicates.py index 3267af7685..04d2f3ea6c 100644 --- a/pype/plugins/standalonepublisher/publish/validate_shots.py +++ b/pype/plugins/standalonepublisher/publish/validate_shot_duplicates.py @@ -2,10 +2,10 @@ import pyblish.api import pype.api -class ValidateShots(pyblish.api.ContextPlugin): - """Validate there is a "mov" next to the editorial file.""" +class ValidateShotDuplicates(pyblish.api.ContextPlugin): + """Validating no duplicate names are in context.""" - label = "Validate Shots" + label = "Validate Shot Duplicates" hosts = ["standalonepublisher"] order = pype.api.ValidateContentsOrder From 9af892eceed4ad0aa943e1bf7d52ff28cd4c5905 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 11:46:01 +0200 Subject: [PATCH 225/947] feat(sp): adding hierarchy and framerange colectors --- .../publish/collect_clips.py | 242 ++++++------- .../publish/collect_editorial.py | 66 ++-- .../publish/collect_frame_ranges.py | 2 +- .../publish/collect_hierarchy.py | 326 ++++++------------ .../publish/collect_shot_names.py | 15 - .../publish/extract_shot.py | 7 +- .../publish/validate_editorial_resources.py | 22 +- 7 files changed, 274 insertions(+), 406 deletions(-) delete mode 100644 pype/plugins/standalonepublisher/publish/collect_shot_names.py diff --git a/pype/plugins/standalonepublisher/publish/collect_clips.py b/pype/plugins/standalonepublisher/publish/collect_clips.py index e38a70f289..d8eb1d72c4 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clips.py +++ b/pype/plugins/standalonepublisher/publish/collect_clips.py @@ -12,7 +12,7 @@ class CollectClips(pyblish.api.InstancePlugin): """Collect Clips instances from editorial's OTIO sequence""" order = pyblish.api.CollectorOrder + 0.01 - label = "Collect Shots" + label = "Collect Clips" hosts = ["standalonepublisher"] families = ["editorial"] @@ -40,8 +40,7 @@ class CollectClips(pyblish.api.InstancePlugin): asset_name = asset_entity["name"] self.log.debug(f"__ asset_entity: `{asset_entity}`") - # Project specific prefix naming. This needs to be replaced with some - # options to be more flexible. + # split selected context asset name asset_name = asset_name.split("_")[0] instances = [] @@ -71,7 +70,6 @@ class CollectClips(pyblish.api.InstancePlugin): if clip.name is None: continue - # Hardcoded to expect a shot name of "[name].[extension]" clip_name = os.path.splitext(clip.name)[0].lower() name = f"{asset_name}_{clip_name}" @@ -109,122 +107,124 @@ class CollectClips(pyblish.api.InstancePlugin): "family": "clip", "families": ["review", "ftrack"], "ftrackFamily": "review", - "representations": [] + "representations": [], + "editorialVideoPath": instance.data[ + "editorialVideoPath"] }) ) - # - # def process_old(self, instance): - # representation = instance.data["representations"][0] - # file_path = os.path.join( - # representation["stagingDir"], representation["files"] - # ) - # instance.context.data["editorialPath"] = file_path - # - # extension = os.path.splitext(file_path)[1][1:] - # kwargs = {} - # if extension == "edl": - # # EDL has no frame rate embedded so needs explicit frame rate else - # # 24 is asssumed. - # kwargs["rate"] = plib.get_asset()["data"]["fps"] - # - # timeline = otio.adapters.read_from_file(file_path, **kwargs) - # tracks = timeline.each_child( - # descended_from_type=otio.schema.track.Track - # ) - # asset_entity = instance.context.data["assetEntity"] - # asset_name = asset_entity["name"] - # - # # Ask user for sequence start. Usually 10:00:00:00. - # sequence_start_frame = 900000 - # - # # Project specific prefix naming. This needs to be replaced with some - # # options to be more flexible. - # asset_name = asset_name.split("_")[0] - # - # instances = [] - # for track in tracks: - # track_start_frame = ( - # abs(track.source_range.start_time.value) - sequence_start_frame - # ) - # for child in track.each_child(): - # # skip all generators like black ampty - # if isinstance( - # child.media_reference, - # otio.schema.GeneratorReference): - # continue - # - # # Transitions are ignored, because Clips have the full frame - # # range. - # if isinstance(child, otio.schema.transition.Transition): - # continue - # - # if child.name is None: - # continue - # - # # Hardcoded to expect a shot name of "[name].[extension]" - # child_name = os.path.splitext(child.name)[0].lower() - # name = f"{asset_name}_{child_name}" - # - # frame_start = track_start_frame - # frame_start += child.range_in_parent().start_time.value - # frame_end = track_start_frame - # frame_end += child.range_in_parent().end_time_inclusive().value - # - # label = f"{name} (framerange: {frame_start}-{frame_end})" - # instances.append( - # instance.context.create_instance(**{ - # "name": name, - # "label": label, - # "frameStart": frame_start, - # "frameEnd": frame_end, - # "family": "shot", - # "families": ["review", "ftrack"], - # "ftrackFamily": "review", - # "asset": name, - # "subset": "shotMain", - # "representations": [], - # "source": file_path - # }) - # ) - # - # visual_hierarchy = [asset_entity] - # while True: - # visual_parent = io.find_one( - # {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - # ) - # if visual_parent: - # visual_hierarchy.append(visual_parent) - # else: - # visual_hierarchy.append(instance.context.data["projectEntity"]) - # break - # - # context_hierarchy = None - # for entity in visual_hierarchy: - # childs = {} - # if context_hierarchy: - # name = context_hierarchy.pop("name") - # childs = {name: context_hierarchy} - # else: - # for instance in instances: - # childs[instance.data["name"]] = { - # "childs": {}, - # "entity_type": "Shot", - # "custom_attributes": { - # "frameStart": instance.data["frameStart"], - # "frameEnd": instance.data["frameEnd"] - # } - # } - # - # context_hierarchy = { - # "entity_type": entity["data"]["entityType"], - # "childs": childs, - # "name": entity["name"] - # } - # - # name = context_hierarchy.pop("name") - # context_hierarchy = {name: context_hierarchy} - # instance.context.data["hierarchyContext"] = context_hierarchy - # self.log.info( - # "Hierarchy:\n" + - # json_util.dumps(context_hierarchy, sort_keys=True, indent=4) - # ) + + def process_old(self, instance): + representation = instance.data["representations"][0] + file_path = os.path.join( + representation["stagingDir"], representation["files"] + ) + instance.context.data["editorialPath"] = file_path + + extension = os.path.splitext(file_path)[1][1:] + kwargs = {} + if extension == "edl": + # EDL has no frame rate embedded so needs explicit frame rate else + # 24 is asssumed. + kwargs["rate"] = plib.get_asset()["data"]["fps"] + + timeline = otio.adapters.read_from_file(file_path, **kwargs) + tracks = timeline.each_child( + descended_from_type=otio.schema.track.Track + ) + asset_entity = instance.context.data["assetEntity"] + asset_name = asset_entity["name"] + + # Ask user for sequence start. Usually 10:00:00:00. + sequence_start_frame = 900000 + + # Project specific prefix naming. This needs to be replaced with some + # options to be more flexible. + asset_name = asset_name.split("_")[0] + + instances = [] + for track in tracks: + track_start_frame = ( + abs(track.source_range.start_time.value) - sequence_start_frame + ) + for child in track.each_child(): + # skip all generators like black ampty + if isinstance( + child.media_reference, + otio.schema.GeneratorReference): + continue + + # Transitions are ignored, because Clips have the full frame + # range. + if isinstance(child, otio.schema.transition.Transition): + continue + + if child.name is None: + continue + + # Hardcoded to expect a shot name of "[name].[extension]" + child_name = os.path.splitext(child.name)[0].lower() + name = f"{asset_name}_{child_name}" + + frame_start = track_start_frame + frame_start += child.range_in_parent().start_time.value + frame_end = track_start_frame + frame_end += child.range_in_parent().end_time_inclusive().value + + label = f"{name} (framerange: {frame_start}-{frame_end})" + instances.append( + instance.context.create_instance(**{ + "name": name, + "label": label, + "frameStart": frame_start, + "frameEnd": frame_end, + "family": "shot", + "families": ["review", "ftrack"], + "ftrackFamily": "review", + "asset": name, + "subset": "shotMain", + "representations": [], + "source": file_path + }) + ) + + visual_hierarchy = [asset_entity] + while True: + visual_parent = io.find_one( + {"_id": visual_hierarchy[-1]["data"]["visualParent"]} + ) + if visual_parent: + visual_hierarchy.append(visual_parent) + else: + visual_hierarchy.append(instance.context.data["projectEntity"]) + break + + context_hierarchy = None + for entity in visual_hierarchy: + childs = {} + if context_hierarchy: + name = context_hierarchy.pop("name") + childs = {name: context_hierarchy} + else: + for instance in instances: + childs[instance.data["name"]] = { + "childs": {}, + "entity_type": "Shot", + "custom_attributes": { + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"] + } + } + + context_hierarchy = { + "entity_type": entity["data"]["entityType"], + "childs": childs, + "name": entity["name"] + } + + name = context_hierarchy.pop("name") + context_hierarchy = {name: context_hierarchy} + instance.context.data["hierarchyContext"] = context_hierarchy + self.log.info( + "Hierarchy:\n" + + json_util.dumps(context_hierarchy, sort_keys=True, indent=4) + ) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index 52acead6cc..baade27ba8 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -37,40 +37,40 @@ class CollectEditorial(pyblish.api.InstancePlugin): def process(self, instance): self.log.debug(f"__ instance: `{instance}`") # get representation with editorial file - representation = instance.data["representations"][0] - - # make editorial sequence file path - staging_dir = representation["stagingDir"] - file_path = os.path.join( - staging_dir, representation["files"] - ) - - # get video file path - video_path = None - basename = os.path.splitext(os.path.basename(file_path))[0] - for f in os.listdir(staging_dir): - self.log.debug(f"__ test file: `{f}`") - # filter out by not sharing the same name - if os.path.splitext(f)[0] not in basename: - continue - # filter out by respected extensions - if os.path.splitext(f)[1] not in self.extensions: - continue - video_path = os.path.join( - staging_dir, f + for representation in instance.data["representations"]: + self.log.debug(f"__ representation: `{representation}`") + # make editorial sequence file path + staging_dir = representation["stagingDir"] + file_path = os.path.join( + staging_dir, str(representation["files"]) ) - self.log.debug(f"__ video_path: `{video_path}`") - instance.context.data["editorialVideoPath"] = video_path - # get editorial sequence file into otio timeline object - extension = os.path.splitext(file_path)[1] - kwargs = {} - if extension == ".edl": - # EDL has no frame rate embedded so needs explicit frame rate else - # 24 is asssumed. - kwargs["rate"] = plib.get_asset()["data"]["fps"] + # get video file path + video_path = None + basename = os.path.splitext(os.path.basename(file_path))[0] + for f in os.listdir(staging_dir): + self.log.debug(f"__ test file: `{f}`") + # filter out by not sharing the same name + if os.path.splitext(f)[0] not in basename: + continue + # filter out by respected extensions + if os.path.splitext(f)[1] not in self.extensions: + continue + video_path = os.path.join( + staging_dir, f + ) + self.log.debug(f"__ video_path: `{video_path}`") + instance.data["editorialVideoPath"] = video_path - instance.data["otio_timeline"] = otio.adapters.read_from_file( - file_path, **kwargs) + # get editorial sequence file into otio timeline object + extension = os.path.splitext(file_path)[1] + kwargs = {} + if extension == ".edl": + # EDL has no frame rate embedded so needs explicit frame rate else + # 24 is asssumed. + kwargs["rate"] = plib.get_asset()["data"]["fps"] - self.log.info(f"Added OTIO timeline from: `{file_path}`") + instance.data["otio_timeline"] = otio.adapters.read_from_file( + file_path, **kwargs) + + self.log.info(f"Added OTIO timeline from: `{file_path}`") diff --git a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py index 5e8292458f..c58cac3505 100644 --- a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py +++ b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py @@ -11,7 +11,7 @@ class CollectClipFrameRanges(pyblish.api.InstancePlugin): # presets start_frame_offset = None # if 900000 for edl default then -900000 - custom_start_frame = None + custom_start_frame = 1 def process(self, instance): diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 3a49b499da..7c968ce302 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -2,7 +2,7 @@ import pyblish.api import avalon.api as avalon import re import os - +from avalon import io class CollectHierarchyInstance(pyblish.api.InstancePlugin): """Collecting hierarchy context from `parents` and `hierarchy` data @@ -19,14 +19,19 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): families = ["clip"] # presets - search_patterns = { - "sequence": r"sc\d{3}", - "shot": r"sh\d{3}", - "episode": r"ep\d{2}" + shot_rename_template = "{project[code]}{_episode_}{clip_name}" + shot_rename_search_patterns = { + "_sequence_": "sc\\d{3}", + "_shot_": "sh\\d{3}", + "_episode_": "ep\\d{2}" } - shot_name_template = "{project[code]}{episode}{clip_name}" - shot_hierarchy = "{episode}{sequence}/{clip_name}" - shot_tasks = ["Animation", "Layout"] + shot_add_hierarchy = { + "parents_path": "{sequence}", + "parents": { + "sequence": "{_episode_}{_sequence_}", + } + } + shot_add_tasks = ["Animation", "Layout"] def convert_to_entity(self, key, value): # ftrack compatible entity types @@ -43,181 +48,103 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): if entity_type: return {"entityType": entity_type, "entityName": value} - def process(self, instance): + def rename_with_hierarchy(self, instance): search_text = "" - context = instance.context - anatomy_data = context.data["anatomyData"] - asset_entity = context.data["assetEntity"] - asset_name = asset_entity["name"] - assets_shared = context.data.get("assetsShared") - + parent_name = self.asset_entity["name"] clip = instance.data["item"] clip_name = os.path.splitext(clip.name)[0].lower() - asset = instance.data["asset"] - clip_in = instance.data["clipIn"] - clip_out = instance.data["clipOut"] - fps = instance.data["fps"] - - hierarchy_data = dict(anatomy_data) - if self.search_patterns: - search_text += clip_name + asset_name - hierarchy_data.update({"clip_name": clip_name}) - for type, pattern in self.search_patterns.items(): + if self.shot_rename_search_patterns: + search_text += parent_name + clip_name + self.hierarchy_data.update({"clip_name": clip_name}) + for type, pattern in self.shot_rename_search_patterns.items(): p = re.compile(pattern) match = p.findall(search_text) if not match: continue - hierarchy_data[type] = match[-1] + self.hierarchy_data[type] = match[-1] - self.log.debug("__ hierarchy_data: {}".format(hierarchy_data)) - shot_name = self.shot_name_template.format(**hierarchy_data) - self.log.debug("__ shot_name: {}".format(shot_name)) - shot_hierarchy = self.shot_hierarchy.format(**hierarchy_data) - self.log.debug("__ shot_hierarchy: {}".format(shot_hierarchy)) + self.log.debug("__ hierarchy_data: {}".format(self.hierarchy_data)) - # # build data for inner nukestudio project property - # data = { - # "sequence": ( - # context.data['activeSequence'].name().replace(' ', '_') - # ), - # "track": clip.parent().name().replace(' ', '_'), - # "clip": asset - # } - # self.log.debug("__ data: {}".format(data)) - # - # # Check for clips with the same range - # # this is for testing if any vertically neighbouring - # # clips has been already processed - # match = next(( - # k for k, v in assets_shared.items() - # if (v["_clipIn"] == clip_in) - # and (v["_clipOut"] == clip_out) - # ), False) - # - # self.log.debug( - # "__ assets_shared[match]: {}".format( - # assets_shared[match])) - # - # # check if hierarchy key is present in matched - # # vertically neighbouring clip - # if not assets_shared[match].get("hierarchy"): - # match = False - # - # # rise exception if multiple hierarchy tag found - # assert not match, ( - # "Two clips above each other with" - # " hierarchy tag are not allowed" - # " >> keep hierarchy tag only in one of them <<" - # ) - # - # d_metadata = dict() - # parents = list() - # - # # main template from Tag.note - # template = t_note - # - # # if shot in template then remove it - # if "shot" in template.lower(): - # instance.data["asset"] = [ - # t for t in template.split('/')][-1] - # template = "/".join( - # [t for t in template.split('/')][0:-1]) - # - # # take template from Tag.note and break it into parts - # template_split = template.split("/") - # patern = re.compile(r"\{([a-z]*?)\}") - # par_split = [patern.findall(t) - # for t in template.split("/")] - # - # # format all {} in two layers - # for k, v in t_metadata.items(): - # new_k = k.split(".")[1] - # - # # ignore all help strings - # if 'help' in k: - # continue - # # self.log.info("__ new_k: `{}`".format(new_k)) - # try: - # # first try all data and context data to - # # add to individual properties - # new_v = str(v).format( - # **dict(context.data, **data)) - # d_metadata[new_k] = new_v - # - # # create parents - # # find matching index of order - # p_match_i = [i for i, p in enumerate(par_split) - # if new_k in p] - # - # # if any is matching then convert to entity_types - # if p_match_i: - # parent = self.convert_to_entity( - # new_k, template_split[p_match_i[0]]) - # parents.insert(p_match_i[0], parent) - # except Exception: - # d_metadata[new_k] = v - # - # # create new shot asset name - # instance.data["asset"] = instance.data["asset"].format( - # **d_metadata) - # self.log.debug( - # "__ instance.data[asset]: " - # "{}".format(instance.data["asset"]) - # ) - # - # # lastly fill those individual properties itno - # # format the string with collected data - # parents = [{"entityName": p["entityName"].format( - # **d_metadata), "entityType": p["entityType"]} - # for p in parents] - # self.log.debug("__ parents: {}".format(parents)) - # - # hierarchy = template.format( - # **d_metadata) - # self.log.debug("__ hierarchy: {}".format(hierarchy)) - # - # # check if hierarchy attribute is already created - # # it should not be so return warning if it is - # hd = instance.data.get("hierarchy") - # assert not hd, ( - # "Only one Hierarchy Tag is allowed. " - # "Clip: `{}`".format(asset) - # ) - # - # # add formated hierarchy path into instance data - # instance.data["hierarchy"] = hierarchy - # instance.data["parents"] = parents - # - # self.log.info( - # "clip: {asset}[{clip_in}:{clip_out}]".format( - # **locals())) - # # adding to asset shared dict - # self.log.debug( - # "__ assets_shared: {}".format(assets_shared)) - # if assets_shared.get(asset): - # self.log.debug("Adding to shared assets: `{}`".format( - # asset)) - # asset_shared = assets_shared.get(asset) - # else: - # asset_shared = assets_shared[asset] - # - # asset_shared.update({ - # "asset": asset, - # "hierarchy": hierarchy, - # "parents": parents, - # "fps": fps, - # "tasks": instance.data["tasks"] - # }) - # - # # adding frame start if any on instance - # start_frame = instance.data.get("startingFrame") - # if start_frame: - # asset_shared.update({ - # "startingFrame": start_frame - # }) - # self.log.debug( - # "assets_shared: {assets_shared}".format(**locals())) + # format to new shot name + self.shot_name = self.shot_rename_template.format( + **self.hierarchy_data) + instance.data["asset"] = self.shot_name + self.log.debug("__ self.shot_name: {}".format(self.shot_name)) + + def create_hierarchy(self, instance): + parents = list() + hierarchy = "" + visual_hierarchy = [self.asset_entity] + while True: + visual_parent = io.find_one( + {"_id": visual_hierarchy[-1]["data"]["visualParent"]} + ) + if visual_parent: + visual_hierarchy.append(visual_parent) + else: + visual_hierarchy.append( + instance.context.data["projectEntity"]) + break + self.log.debug("__ visual_hierarchy: {}".format(visual_hierarchy)) + + # add current selection context hierarchy from standalonepublisher + for entity in reversed(visual_hierarchy): + parents.append({ + "entityType": entity["data"]["entityType"], + "entityName": entity["name"] + }) + + if self.shot_add_hierarchy: + # fill the parents parts from presets + for parent in self.shot_add_hierarchy["parents"]: + if not self.shot_add_hierarchy["parents"][parent]: + prnt = {"entity"} + else: + self.shot_add_hierarchy["parents"][parent] = self.shot_add_hierarchy[ + "parents"][parent].format(**self.hierarchy_data) + prnt = self.convert_to_entity( + parent, self.shot_add_hierarchy["parents"][parent]) + parents.append(prnt) + + hierarchy = self.shot_add_hierarchy[ + "parents_path"].format(**self.shot_add_hierarchy["parents"]) + + instance.data["hierarchy"] = hierarchy + instance.data["parents"] = parents + + if self.shot_add_tasks: + instance.data["tasks"] = self.shot_add_tasks + else: + instance.data["tasks"] = list() + + def process(self, instance): + assets_shared = instance.context.data.get("assetsShared") + context = instance.context + anatomy_data = context.data["anatomyData"] + + self.shot_name = instance.data["asset"] + self.hierarchy_data = dict(anatomy_data) + self.asset_entity = context.data["assetEntity"] + + frame_start = instance.data["frameStart"] + frame_end = instance.data["frameEnd"] + + if self.shot_rename_template: + self.rename_with_hierarchy(instance) + + self.create_hierarchy(instance) + + label = f"{self.shot_name} ({frame_start}-{frame_end})" + instance.data["label"] = label + + assets_shared[self.shot_name] = { + "asset": instance.data["asset"], + "hierarchy": instance.data["hierarchy"], + "parents": instance.data["parents"], + "fps": instance.data["fps"], + "tasks": instance.data["tasks"] + } class CollectHierarchyContext(pyblish.api.ContextPlugin): @@ -242,13 +169,12 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): return new_dict def process(self, context): - instances = context[:] - + instances = context # create hierarchyContext attr if context has none - temp_context = {} + final_context = {} for instance in instances: - if 'projectfile' in instance.data.get('family', ''): + if 'clip' not in instance.data.get('family', ''): continue name = instance.data["asset"] @@ -268,11 +194,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): instance.data["parents"] = s_asset_data["parents"] instance.data["hierarchy"] = s_asset_data["hierarchy"] instance.data["tasks"] = s_asset_data["tasks"] - instance.data["resolutionWidth"] = s_asset_data[ - "resolutionWidth"] - instance.data["resolutionHeight"] = s_asset_data[ - "resolutionHeight"] - instance.data["pixelAspect"] = s_asset_data["pixelAspect"] instance.data["fps"] = s_asset_data["fps"] # adding frame start if any on instance @@ -283,8 +204,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): instance.data["clipOut"] - instance.data["clipIn"]) - - self.log.debug( "__ instance.data[parents]: {}".format( instance.data["parents"] @@ -301,32 +220,20 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): in_info = {} - in_info["inputs"] = [ - x["_id"] for x in instance.data.get("assetbuilds", []) - ] - # suppose that all instances are Shots in_info['entity_type'] = 'Shot' # get custom attributes of the shot - if instance.data.get("main"): - in_info['custom_attributes'] = { - "handleStart": handle_start, - "handleEnd": handle_end, - "frameStart": instance.data["frameStart"], - "frameEnd": instance.data["frameEnd"], - "clipIn": instance.data["clipIn"], - "clipOut": instance.data["clipOut"], - 'fps': instance.context.data["fps"] - } - # adding SourceResolution if Tag was present - if instance.data.get("main"): - in_info['custom_attributes'].update({ - "resolutionWidth": instance.data["resolutionWidth"], - "resolutionHeight": instance.data["resolutionHeight"], - "pixelAspect": instance.data["pixelAspect"] - }) + in_info['custom_attributes'] = { + "handleStart": handle_start, + "handleEnd": handle_end, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "clipIn": instance.data["clipIn"], + "clipOut": instance.data["clipOut"], + 'fps': instance.data["fps"] + } in_info['tasks'] = instance.data['tasks'] @@ -343,14 +250,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): next_dict[parent_name]["childs"] = actual actual = next_dict - temp_context = self.update_dict(temp_context, actual) - - # TODO: 100% sure way of get project! Will be Name or Code? - project_name = avalon.Session["AVALON_PROJECT"] - final_context = {} - final_context[project_name] = {} - final_context[project_name]['entity_type'] = 'Project' - final_context[project_name]['childs'] = temp_context + final_context = self.update_dict(final_context, actual) # adding hierarchy context to instance context.data["hierarchyContext"] = final_context diff --git a/pype/plugins/standalonepublisher/publish/collect_shot_names.py b/pype/plugins/standalonepublisher/publish/collect_shot_names.py deleted file mode 100644 index 7976c855d3..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_shot_names.py +++ /dev/null @@ -1,15 +0,0 @@ -import pyblish.api -import re - - -class CollectShotNames(pyblish.api.InstancePlugin): - """ - Collecting shot names - """ - - label = "Collect shot names" - order = pyblish.api.CollectorOrder + 0.01 - hosts = ["standalonepublisher"] - - def process(self, instance): - self.log.info("Instance name: `{}`".format(instance.data["name"])) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot.py b/pype/plugins/standalonepublisher/publish/extract_shot.py index e1e69d993b..724c22cb1a 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot.py @@ -12,9 +12,6 @@ class ExtractShot(pype.api.Extractor): families = ["clip"] def process(self, instance): - # get context - context = instance.context - # get ffmpet path ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") @@ -24,7 +21,7 @@ class ExtractShot(pype.api.Extractor): # Generate mov file. fps = instance.data["fps"] - video_file_path = context.data["editorialVideoPath"] + video_file_path = instance.data["editorialVideoPath"] ext = os.path.splitext(os.path.basename(video_file_path))[-1] clip_trimed_path = os.path.join( @@ -60,7 +57,7 @@ class ExtractShot(pype.api.Extractor): "frameEnd": instance.data["frameEnd"], "fps": fps, "thumbnail": True, - "tags": ["review", "ftrackreview"] + "tags": ["review", "ftrackreview", "delete"] }) # # Generate jpegs. diff --git a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py index e4d2279317..6033ed919b 100644 --- a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py +++ b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py @@ -9,24 +9,10 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): label = "Validate Editorial Resources" hosts = ["standalonepublisher"] - families = ["editorial"] + families = ["clip"] order = pype.api.ValidateContentsOrder - # presets - check_ext = None - def process(self, instance): - check_ext = self.check_ext or "mov" - representation = instance.data["representations"][0] - staging_dir = representation["stagingDir"] - basename = os.path.splitext( - os.path.basename(representation["files"]) - )[0] - - files = [x for x in os.listdir(staging_dir)] - - # Check for correct extansion in file name. - filename = basename + check_ext - filepath = os.path.join(staging_dir, filename) - msg = f"Missing \"{filepath}\"." - assert filename in files, msg + check_file = instance.data["editorialVideoPath"] + msg = f"Missing \"{check_file}\"." + assert check_file, msg From b41cca9972ec5ebac3047f60a69bd37c8be79e50 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 12:53:13 +0200 Subject: [PATCH 226/947] feat(sp): refactory way the publish gui is started so preset are loaded to plugins --- pype/modules/standalonepublish/publish.py | 120 +++++++++------------- 1 file changed, 47 insertions(+), 73 deletions(-) diff --git a/pype/modules/standalonepublish/publish.py b/pype/modules/standalonepublish/publish.py index dd65030f7a..d2d9eb486f 100644 --- a/pype/modules/standalonepublish/publish.py +++ b/pype/modules/standalonepublish/publish.py @@ -6,13 +6,14 @@ import random import string from avalon import io, api -from avalon.tools import publish as av_publish - +import importlib import pype -from pype.api import execute +from pype.api import execute, Logger import pyblish.api -from . import PUBLISH_PATHS + + +log = Logger().get_logger("standalonepublisher") def set_context(project, asset, task, app): @@ -65,64 +66,12 @@ def publish(data, gui=True): # avalon_api_publish(data, gui) -def avalon_api_publish(data, gui=True): - ''' Launches Pyblish (GUI by default) - :param data: Should include data for pyblish and standalone collector - :type data: dict - :param gui: Pyblish will be launched in GUI mode if set to True - :type gui: bool - ''' - io.install() - - # Create hash name folder in temp - chars = "".join([random.choice(string.ascii_letters) for i in range(15)]) - staging_dir = tempfile.mkdtemp(chars) - - # create also json and fill with data - json_data_path = staging_dir + os.path.basename(staging_dir) + '.json' - with open(json_data_path, 'w') as outfile: - json.dump(data, outfile) - - args = [ - "-pp", os.pathsep.join(pyblish.api.registered_paths()) - ] - - envcopy = os.environ.copy() - envcopy["PYBLISH_HOSTS"] = "standalonepublisher" - envcopy["SAPUBLISH_INPATH"] = json_data_path - - if gui: - av_publish.show() - else: - returncode = execute([ - sys.executable, "-u", "-m", "pyblish" - ] + args, env=envcopy) - - io.uninstall() - - def cli_publish(data, gui=True): + from . import PUBLISH_PATHS + + PUBLISH_SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "publish.py") io.install() - pyblish.api.deregister_all_plugins() - # Registers Global pyblish plugins - pype.install() - # Registers Standalone pyblish plugins - for path in PUBLISH_PATHS: - pyblish.api.register_plugin_path(path) - - project_plugins_paths = os.environ.get("PYPE_PROJECT_PLUGINS") - project_name = os.environ["AVALON_PROJECT"] - if project_plugins_paths and project_name: - for path in project_plugins_paths.split(os.pathsep): - if not path: - continue - plugin_path = os.path.join(path, project_name, "plugins") - if os.path.exists(plugin_path): - pyblish.api.register_plugin_path(plugin_path) - api.register_plugin_path(api.Loader, plugin_path) - api.register_plugin_path(api.Creator, plugin_path) - # Create hash name folder in temp chars = "".join([random.choice(string.ascii_letters) for i in range(15)]) staging_dir = tempfile.mkdtemp(chars) @@ -136,30 +85,55 @@ def cli_publish(data, gui=True): with open(json_data_path, 'w') as outfile: json.dump(data, outfile) - args = [ - "-pp", os.pathsep.join(pyblish.api.registered_paths()) - ] - - if gui: - args += ["gui"] - envcopy = os.environ.copy() envcopy["PYBLISH_HOSTS"] = "standalonepublisher" envcopy["SAPUBLISH_INPATH"] = json_data_path envcopy["SAPUBLISH_OUTPATH"] = return_data_path - envcopy["PYBLISH_GUI"] = "pyblish_pype" + envcopy["PYBLISHGUI"] = "pyblish_pype" + envcopy["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) - returncode = execute([ - sys.executable, "-u", "-m", "pyblish" - ] + args, env=envcopy) + result = execute( + [sys.executable, PUBLISH_SCRIPT_PATH], + env=envcopy + ) result = {} if os.path.exists(json_data_path): with open(json_data_path, "r") as f: result = json.load(f) + log.info(f"Publish result: {result}") + io.uninstall() - # TODO: check if was pyblish successful - # if successful return True - print('Check result here') + return False + + +def main(env): + from avalon.tools import publish + # Registers pype's Global pyblish plugins + pype.install() + + # Register additional paths + addition_paths_str = env.get("PUBLISH_PATHS") or "" + addition_paths = addition_paths_str.split(os.pathsep) + for path in addition_paths: + path = os.path.normpath(path) + if not os.path.exists(path): + continue + pyblish.api.register_plugin_path(path) + + # Register project specific plugins + project_name = os.environ["AVALON_PROJECT"] + project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or "" + for path in project_plugins_paths.split(os.pathsep): + plugin_path = os.path.join(path, project_name, "plugins") + if os.path.exists(plugin_path): + pyblish.api.register_plugin_path(plugin_path) + + return publish.show() + + +if __name__ == "__main__": + result = main(os.environ) + sys.exit(not bool(result)) From 04c4ba77626b3c89df66b053b6f511fd0b5a2926 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 16:51:20 +0200 Subject: [PATCH 227/947] feat(global): moving to correct host folder --- .../publish/integrate_ftrack_component_overwrite.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/plugins/{premiere => ftrack}/publish/integrate_ftrack_component_overwrite.py (100%) diff --git a/pype/plugins/premiere/publish/integrate_ftrack_component_overwrite.py b/pype/plugins/ftrack/publish/integrate_ftrack_component_overwrite.py similarity index 100% rename from pype/plugins/premiere/publish/integrate_ftrack_component_overwrite.py rename to pype/plugins/ftrack/publish/integrate_ftrack_component_overwrite.py From c44873d2c1a56d8bbde6d80a788aa39e1c2fa333 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 16:52:12 +0200 Subject: [PATCH 228/947] feat(global): adding standalonepublisher to hosts --- pype/plugins/global/publish/extract_burnin.py | 9 ++++++++- pype/plugins/global/publish/extract_review.py | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 83ad4af1c2..e1508b9131 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -19,7 +19,14 @@ class ExtractBurnin(pype.api.Extractor): label = "Extract burnins" order = pyblish.api.ExtractorOrder + 0.03 families = ["review", "burnin"] - hosts = ["nuke", "maya", "shell", "nukestudio", "premiere"] + hosts = [ + "nuke", + "maya", + "shell", + "nukestudio", + "premiere", + "standalonepublisher" + ] optional = True positions = [ diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 30d1de8328..a16c3ce256 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -22,7 +22,15 @@ class ExtractReview(pyblish.api.InstancePlugin): label = "Extract Review" order = pyblish.api.ExtractorOrder + 0.02 families = ["review"] - hosts = ["nuke", "maya", "shell", "nukestudio", "premiere", "harmony"] + hosts = [ + "nuke", + "maya", + "shell", + "nukestudio", + "premiere", + "harmony", + "standalonepublisher" + ] # Supported extensions image_exts = ["exr", "jpg", "jpeg", "png", "dpx"] From 33d671edd9d0b6111aeb95e80c2aeac95d9ee46f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 16:55:02 +0200 Subject: [PATCH 229/947] feat(sp): updating extract_shot --- pype/plugins/standalonepublisher/publish/extract_shot.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot.py b/pype/plugins/standalonepublisher/publish/extract_shot.py index 724c22cb1a..7112bea15b 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot.py @@ -48,6 +48,10 @@ class ExtractShot(pype.api.Extractor): output = pype.api.subprocess(ffmpeg_args) self.log.info(output) + instance.data["families"].remove("review") + instance.data["families"].append("clip") + instance.data["family"] = "review" + instance.data["representations"].append({ "name": ext[1:], "ext": ext[1:], @@ -60,6 +64,8 @@ class ExtractShot(pype.api.Extractor): "tags": ["review", "ftrackreview", "delete"] }) + self.log.debug(f"Instance data: {instance.data}") + # # Generate jpegs. # clip_thumbnail = os.path.join( # staging_dir, instance.data["name"] + ".%04d.jpeg" From 83d35608a10ac392e6dc40c1dacc87b8eee0fca3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 16:55:52 +0200 Subject: [PATCH 230/947] clean(sp): make it nicer --- .../standalonepublisher/publish/collect_editorial.py | 6 +++--- .../standalonepublisher/publish/collect_hierarchy.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index baade27ba8..c710802ec0 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -2,7 +2,7 @@ import os import opentimelineio as otio import pyblish.api from pype import lib as plib -import pype.api + class OTIO_View(pyblish.api.Action): """Currently disabled because OTIO requires PySide2. Issue on Qt.py: @@ -66,8 +66,8 @@ class CollectEditorial(pyblish.api.InstancePlugin): extension = os.path.splitext(file_path)[1] kwargs = {} if extension == ".edl": - # EDL has no frame rate embedded so needs explicit frame rate else - # 24 is asssumed. + # EDL has no frame rate embedded so needs explicit + # frame rate else 24 is asssumed. kwargs["rate"] = plib.get_asset()["data"]["fps"] instance.data["otio_timeline"] = otio.adapters.read_from_file( diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 7c968ce302..4207b0824f 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -1,9 +1,9 @@ import pyblish.api -import avalon.api as avalon import re import os from avalon import io + class CollectHierarchyInstance(pyblish.api.InstancePlugin): """Collecting hierarchy context from `parents` and `hierarchy` data present in `clip` family instances coming from the request json data file From a07038d889751e29685f20353933630f2a4fbc6f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 16:56:24 +0200 Subject: [PATCH 231/947] feat(sp): adding required current file --- pype/plugins/standalonepublisher/publish/collect_editorial.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index c710802ec0..e11ea8c31f 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -44,6 +44,7 @@ class CollectEditorial(pyblish.api.InstancePlugin): file_path = os.path.join( staging_dir, str(representation["files"]) ) + instance.context.data["currentFile"] = file_path # get video file path video_path = None From 005e39b33c2679c4b182db6e295384a9e2de9704 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 17:00:57 +0200 Subject: [PATCH 232/947] feat(sp): removing preset-able content --- .../publish/collect_hierarchy.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 4207b0824f..dcae3e7e87 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -19,19 +19,10 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): families = ["clip"] # presets - shot_rename_template = "{project[code]}{_episode_}{clip_name}" - shot_rename_search_patterns = { - "_sequence_": "sc\\d{3}", - "_shot_": "sh\\d{3}", - "_episode_": "ep\\d{2}" - } - shot_add_hierarchy = { - "parents_path": "{sequence}", - "parents": { - "sequence": "{_episode_}{_sequence_}", - } - } - shot_add_tasks = ["Animation", "Layout"] + shot_rename_template = None + shot_rename_search_patterns = None + shot_add_hierarchy = None + shot_add_tasks = None def convert_to_entity(self, key, value): # ftrack compatible entity types From 1e73ba2397bb48a51921d0aa6edab76817f4d982 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 17:04:06 +0200 Subject: [PATCH 233/947] feat(sp): removing preset-able content --- pype/plugins/standalonepublisher/publish/collect_editorial.py | 2 +- .../plugins/standalonepublisher/publish/collect_frame_ranges.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index e11ea8c31f..fa5b5f13a3 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -32,7 +32,7 @@ class CollectEditorial(pyblish.api.InstancePlugin): actions = [] # presets - extensions = [".mov", ".mp4"] + extensions = [".mov"] def process(self, instance): self.log.debug(f"__ instance: `{instance}`") diff --git a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py index c58cac3505..5e8292458f 100644 --- a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py +++ b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py @@ -11,7 +11,7 @@ class CollectClipFrameRanges(pyblish.api.InstancePlugin): # presets start_frame_offset = None # if 900000 for edl default then -900000 - custom_start_frame = 1 + custom_start_frame = None def process(self, instance): From 17e3c19d7a3db4b2bafb9e6dd326179b07f034ab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 19:43:53 +0200 Subject: [PATCH 234/947] feat(sa): publishing clips wip --- .../standalonepublisher/publish/collect_clips.py | 4 ++-- .../publish/collect_hierarchy.py | 9 +++++++++ .../standalonepublisher/publish/extract_shot.py | 16 +++++++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_clips.py b/pype/plugins/standalonepublisher/publish/collect_clips.py index d8eb1d72c4..2fc44f8aeb 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clips.py +++ b/pype/plugins/standalonepublisher/publish/collect_clips.py @@ -99,8 +99,8 @@ class CollectClips(pyblish.api.InstancePlugin): "clipIn": clip_in, "clipOut": clip_out, "clipDuration": clip_duration, - "handleStart": asset_data["handleStart"], - "handleEnd": asset_data["handleEnd"], + "handleStart": int(asset_data["handleStart"]), + "handleEnd": int(asset_data["handleEnd"]), "fps": fps, # instance properities diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index dcae3e7e87..6d5ab4b94a 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -109,6 +109,12 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): else: instance.data["tasks"] = list() + # updating hierarchy data + self.hierarchy_data.update({ + "asset": self.shot_name, + "task": "conform" + }) + def process(self, instance): assets_shared = instance.context.data.get("assetsShared") context = instance.context @@ -126,6 +132,9 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): self.create_hierarchy(instance) + # adding anatomyData for burnins + instance.data["anatomyData"] = self.hierarchy_data + label = f"{self.shot_name} ({frame_start}-{frame_end})" instance.data["label"] = label diff --git a/pype/plugins/standalonepublisher/publish/extract_shot.py b/pype/plugins/standalonepublisher/publish/extract_shot.py index 7112bea15b..907773b90e 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot.py @@ -3,6 +3,8 @@ import clique import pype.api import pype.lib as plib +from pprint import pformat + class ExtractShot(pype.api.Extractor): """Extract shot "mov" and "wav" files.""" @@ -52,19 +54,27 @@ class ExtractShot(pype.api.Extractor): instance.data["families"].append("clip") instance.data["family"] = "review" + # frame ranges + frame_start = int(instance.data["frameStart"]) + frame_end = int(instance.data["frameEnd"]) + handle_start = int(instance.data["handleStart"]) + handle_end = int(instance.data["handleEnd"]) + instance.data["representations"].append({ "name": ext[1:], "ext": ext[1:], "files": os.path.basename(clip_trimed_path), "stagingDir": staging_dir, - "frameStart": instance.data["frameStart"], - "frameEnd": instance.data["frameEnd"], + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartFtrack": frame_start - handle_start, + "frameEndFtrack": frame_end - handle_end, "fps": fps, "thumbnail": True, "tags": ["review", "ftrackreview", "delete"] }) - self.log.debug(f"Instance data: {instance.data}") + self.log.debug(f"Instance data: {pformat(instance.data)}") # # Generate jpegs. # clip_thumbnail = os.path.join( From 8f57ae45fe70a72abceea9a4993e48d2e57e87c7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 4 Aug 2020 21:02:44 +0200 Subject: [PATCH 235/947] feat(sp): wip publishing clips --- .../publish/collect_hierarchy_context.py | 1 + .../publish/collect_clips.py | 292 +++++++++--------- .../publish/collect_hierarchy.py | 15 +- .../publish/collect_shots.py | 37 +++ .../publish/extract_shot.py | 108 ++++--- 5 files changed, 273 insertions(+), 180 deletions(-) create mode 100644 pype/plugins/standalonepublisher/publish/collect_shots.py diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 38040f8c51..a41e987bdb 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -210,6 +210,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): self.log.debug( "assets_shared: {assets_shared}".format(**locals())) + class CollectHierarchyContext(pyblish.api.ContextPlugin): '''Collecting Hierarchy from instaces and building context hierarchy tree diff --git a/pype/plugins/standalonepublisher/publish/collect_clips.py b/pype/plugins/standalonepublisher/publish/collect_clips.py index 2fc44f8aeb..6a00675961 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clips.py +++ b/pype/plugins/standalonepublisher/publish/collect_clips.py @@ -84,147 +84,159 @@ class CollectClips(pyblish.api.InstancePlugin): label = f"{name} (framerange: {clip_in}-{clip_out})" + new_instance_data = { + # shared attributes + "representations": [], + # timing properities + "trackStartFrame": track_start_frame, + "sourceIn": source_in, + "sourceOut": source_in + clip_duration, + "clipIn": clip_in, + "clipOut": clip_out, + "clipDuration": clip_duration, + "handleStart": int(asset_data["handleStart"]), + "handleEnd": int(asset_data["handleEnd"]), + "fps": fps + } + + # adding Review-able instance + shot_instance_data = new_instance_data.copy() + shot_instance_data.update({ + # unique attributes + "name": name, + "label": label, + "asset": name, + "subset": "plateRef", + "item": clip, + + # instance properities + "family": "clip", + "families": ["review", "ftrack"], + "ftrackFamily": "review", + "editorialVideoPath": instance.data[ + "editorialVideoPath"] + }) instances.append( - instance.context.create_instance(**{ - "name": name, - "label": label, - "asset": name, - "subset": "plateRef", - "item": clip, - - # timing properities - "trackStartFrame": track_start_frame, - "sourceIn": source_in, - "sourceOut": source_in + clip_duration, - "clipIn": clip_in, - "clipOut": clip_out, - "clipDuration": clip_duration, - "handleStart": int(asset_data["handleStart"]), - "handleEnd": int(asset_data["handleEnd"]), - "fps": fps, - - # instance properities - "family": "clip", - "families": ["review", "ftrack"], - "ftrackFamily": "review", - "representations": [], - "editorialVideoPath": instance.data[ - "editorialVideoPath"] - }) + instance.context.create_instance(**shot_instance_data) ) - def process_old(self, instance): - representation = instance.data["representations"][0] - file_path = os.path.join( - representation["stagingDir"], representation["files"] - ) - instance.context.data["editorialPath"] = file_path + context.data["assetsShared"][name] = { + "_clipIn": clip_in, + "_clipOut": clip_out + } - extension = os.path.splitext(file_path)[1][1:] - kwargs = {} - if extension == "edl": - # EDL has no frame rate embedded so needs explicit frame rate else - # 24 is asssumed. - kwargs["rate"] = plib.get_asset()["data"]["fps"] - - timeline = otio.adapters.read_from_file(file_path, **kwargs) - tracks = timeline.each_child( - descended_from_type=otio.schema.track.Track - ) - asset_entity = instance.context.data["assetEntity"] - asset_name = asset_entity["name"] - - # Ask user for sequence start. Usually 10:00:00:00. - sequence_start_frame = 900000 - - # Project specific prefix naming. This needs to be replaced with some - # options to be more flexible. - asset_name = asset_name.split("_")[0] - - instances = [] - for track in tracks: - track_start_frame = ( - abs(track.source_range.start_time.value) - sequence_start_frame - ) - for child in track.each_child(): - # skip all generators like black ampty - if isinstance( - child.media_reference, - otio.schema.GeneratorReference): - continue - - # Transitions are ignored, because Clips have the full frame - # range. - if isinstance(child, otio.schema.transition.Transition): - continue - - if child.name is None: - continue - - # Hardcoded to expect a shot name of "[name].[extension]" - child_name = os.path.splitext(child.name)[0].lower() - name = f"{asset_name}_{child_name}" - - frame_start = track_start_frame - frame_start += child.range_in_parent().start_time.value - frame_end = track_start_frame - frame_end += child.range_in_parent().end_time_inclusive().value - - label = f"{name} (framerange: {frame_start}-{frame_end})" - instances.append( - instance.context.create_instance(**{ - "name": name, - "label": label, - "frameStart": frame_start, - "frameEnd": frame_end, - "family": "shot", - "families": ["review", "ftrack"], - "ftrackFamily": "review", - "asset": name, - "subset": "shotMain", - "representations": [], - "source": file_path - }) - ) - - visual_hierarchy = [asset_entity] - while True: - visual_parent = io.find_one( - {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - ) - if visual_parent: - visual_hierarchy.append(visual_parent) - else: - visual_hierarchy.append(instance.context.data["projectEntity"]) - break - - context_hierarchy = None - for entity in visual_hierarchy: - childs = {} - if context_hierarchy: - name = context_hierarchy.pop("name") - childs = {name: context_hierarchy} - else: - for instance in instances: - childs[instance.data["name"]] = { - "childs": {}, - "entity_type": "Shot", - "custom_attributes": { - "frameStart": instance.data["frameStart"], - "frameEnd": instance.data["frameEnd"] - } - } - - context_hierarchy = { - "entity_type": entity["data"]["entityType"], - "childs": childs, - "name": entity["name"] - } - - name = context_hierarchy.pop("name") - context_hierarchy = {name: context_hierarchy} - instance.context.data["hierarchyContext"] = context_hierarchy - self.log.info( - "Hierarchy:\n" + - json_util.dumps(context_hierarchy, sort_keys=True, indent=4) - ) + # def process_old(self, instance): + # representation = instance.data["representations"][0] + # file_path = os.path.join( + # representation["stagingDir"], representation["files"] + # ) + # instance.context.data["editorialPath"] = file_path + # + # extension = os.path.splitext(file_path)[1][1:] + # kwargs = {} + # if extension == "edl": + # # EDL has no frame rate embedded so needs explicit frame rate else + # # 24 is asssumed. + # kwargs["rate"] = plib.get_asset()["data"]["fps"] + # + # timeline = otio.adapters.read_from_file(file_path, **kwargs) + # tracks = timeline.each_child( + # descended_from_type=otio.schema.track.Track + # ) + # asset_entity = instance.context.data["assetEntity"] + # asset_name = asset_entity["name"] + # + # # Ask user for sequence start. Usually 10:00:00:00. + # sequence_start_frame = 900000 + # + # # Project specific prefix naming. This needs to be replaced with some + # # options to be more flexible. + # asset_name = asset_name.split("_")[0] + # + # instances = [] + # for track in tracks: + # track_start_frame = ( + # abs(track.source_range.start_time.value) - sequence_start_frame + # ) + # for child in track.each_child(): + # # skip all generators like black ampty + # if isinstance( + # child.media_reference, + # otio.schema.GeneratorReference): + # continue + # + # # Transitions are ignored, because Clips have the full frame + # # range. + # if isinstance(child, otio.schema.transition.Transition): + # continue + # + # if child.name is None: + # continue + # + # # Hardcoded to expect a shot name of "[name].[extension]" + # child_name = os.path.splitext(child.name)[0].lower() + # name = f"{asset_name}_{child_name}" + # + # frame_start = track_start_frame + # frame_start += child.range_in_parent().start_time.value + # frame_end = track_start_frame + # frame_end += child.range_in_parent().end_time_inclusive().value + # + # label = f"{name} (framerange: {frame_start}-{frame_end})" + # instances.append( + # instance.context.create_instance(**{ + # "name": name, + # "label": label, + # "frameStart": frame_start, + # "frameEnd": frame_end, + # "family": "shot", + # "families": ["review", "ftrack"], + # "ftrackFamily": "review", + # "asset": name, + # "subset": "shotMain", + # "representations": [], + # "source": file_path + # }) + # ) + # + # visual_hierarchy = [asset_entity] + # while True: + # visual_parent = io.find_one( + # {"_id": visual_hierarchy[-1]["data"]["visualParent"]} + # ) + # if visual_parent: + # visual_hierarchy.append(visual_parent) + # else: + # visual_hierarchy.append(instance.context.data["projectEntity"]) + # break + # + # context_hierarchy = None + # for entity in visual_hierarchy: + # childs = {} + # if context_hierarchy: + # name = context_hierarchy.pop("name") + # childs = {name: context_hierarchy} + # else: + # for instance in instances: + # childs[instance.data["name"]] = { + # "childs": {}, + # "entity_type": "Shot", + # "custom_attributes": { + # "frameStart": instance.data["frameStart"], + # "frameEnd": instance.data["frameEnd"] + # } + # } + # + # context_hierarchy = { + # "entity_type": entity["data"]["entityType"], + # "childs": childs, + # "name": entity["name"] + # } + # + # name = context_hierarchy.pop("name") + # context_hierarchy = {name: context_hierarchy} + # instance.context.data["hierarchyContext"] = context_hierarchy + # self.log.info( + # "Hierarchy:\n" + + # json_util.dumps(context_hierarchy, sort_keys=True, indent=4) + # ) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 6d5ab4b94a..87878d560e 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -116,6 +116,7 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): }) def process(self, instance): + asset = instance.data["asset"] assets_shared = instance.context.data.get("assetsShared") context = instance.context anatomy_data = context.data["anatomyData"] @@ -138,13 +139,23 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): label = f"{self.shot_name} ({frame_start}-{frame_end})" instance.data["label"] = label - assets_shared[self.shot_name] = { + # dealing with shared attributes across instances + # with the same asset name + + if assets_shared.get(asset): + self.log.debug("Adding to shared assets: `{}`".format( + asset)) + asset_shared = assets_shared.get(asset) + else: + asset_shared = assets_shared[asset] + + asset_shared.update({ "asset": instance.data["asset"], "hierarchy": instance.data["hierarchy"], "parents": instance.data["parents"], "fps": instance.data["fps"], "tasks": instance.data["tasks"] - } + }) class CollectHierarchyContext(pyblish.api.ContextPlugin): diff --git a/pype/plugins/standalonepublisher/publish/collect_shots.py b/pype/plugins/standalonepublisher/publish/collect_shots.py new file mode 100644 index 0000000000..269a65fd86 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_shots.py @@ -0,0 +1,37 @@ +from pyblish import api + + +class CollectShots(api.InstancePlugin): + """Collect Shot from Clip.""" + + # Run just before CollectClipSubsets + order = api.CollectorOrder + 0.1021 + label = "Collect Shots" + hosts = ["standalonepublisher"] + families = ["clip"] + + def process(self, instance): + + # Collect data. + data = {} + for key, value in instance.data.items(): + data[key] = value + + data["family"] = "shot" + data["families"] = [] + + data["subset"] = data["family"] + "Main" + + data["name"] = data["subset"] + "_" + data["asset"] + + data["label"] = ( + "{} - {} - tasks:{}".format( + data["asset"], + data["subset"], + data["tasks"] + ) + ) + + # Create instance. + self.log.debug("Creating instance with: {}".format(data["name"])) + instance.context.create_instance(**data) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot.py b/pype/plugins/standalonepublisher/publish/extract_shot.py index 907773b90e..7c1486d7b6 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot.py @@ -13,6 +13,10 @@ class ExtractShot(pype.api.Extractor): hosts = ["standalonepublisher"] families = ["clip"] + # presets + add_representation = None # ".jpeg" + add_audio = True + def process(self, instance): # get ffmpet path ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") @@ -76,41 +80,69 @@ class ExtractShot(pype.api.Extractor): self.log.debug(f"Instance data: {pformat(instance.data)}") - # # Generate jpegs. - # clip_thumbnail = os.path.join( - # staging_dir, instance.data["name"] + ".%04d.jpeg" - # ) - # args = [ffmpeg_path, "-i", clip_trimed_path, clip_thumbnail] - # self.log.info(f"Processing: {args}") - # output = pype.lib._subprocess(args) - # self.log.info(output) - # - # # collect jpeg sequence if editorial data for publish - # # are image sequence - # collection = clique.Collection( - # head=instance.data["name"] + ".", tail='.jpeg', padding=4 - # ) - # for f in os.listdir(staging_dir): - # if collection.match(f): - # collection.add(f) - # - # instance.data["representations"].append({ - # "name": "jpeg", - # "ext": "jpeg", - # "files": list(collection), - # "stagingDir": staging_dir - # }) - # - # # Generate wav file. - # shot_wav = os.path.join(staging_dir, instance.data["name"] + ".wav") - # args = [ffmpeg_path, "-i", clip_trimed_path, shot_wav] - # self.log.info(f"Processing: {args}") - # output = pype.lib._subprocess(args) - # self.log.info(output) - # - # instance.data["representations"].append({ - # "name": "wav", - # "ext": "wav", - # "files": os.path.basename(shot_wav), - # "stagingDir": staging_dir - # }) + if self.add_representation: + # Generate jpegs. + clip_img_sequence = os.path.join( + staging_dir, instance.data["name"] + ".%04d.jpeg" + ) + args = [ffmpeg_path, "-i", clip_trimed_path, clip_img_sequence] + self.log.info(f"Processing: {args}") + output = pype.lib._subprocess(args) + self.log.info(output) + + # collect jpeg sequence if editorial data for publish + # are image sequence + collection = clique.Collection( + head=instance.data["name"] + ".", tail='.jpeg', padding=4 + ) + for f in os.listdir(staging_dir): + if collection.match(f): + collection.add(f) + + instance.data["representations"].append({ + "name": "jpeg", + "ext": "jpeg", + "files": list(collection), + "stagingDir": staging_dir + }) + + if self.add_audio: + audio_ext = ".wav" + # Generate wav file. + shot_wav = os.path.join( + staging_dir, instance.data["name"] + audio_ext) + # Collect data. + data = {} + for key, value in instance.data.items(): + data[key] = value + + data["family"] = "audio" + data["families"] = ["ftrack"] + + data["subset"] = "audioMain" + + data["source"] = shot_wav + + data["name"] = data["subset"] + "_" + data["asset"] + + data["label"] = "{} - {} - ({})".format( + data['asset'], + data["subset"], + audio_ext + ) + + # Create instance. + self.log.debug("Creating instance with: {}".format(data["name"])) + instance = instance.context.create_instance(**data) + + args = [ffmpeg_path, "-i", clip_trimed_path, shot_wav] + self.log.info(f"Processing: {args}") + output = pype.lib._subprocess(args) + self.log.info(output) + + instance.data["representations"] = [{ + "name": "wav", + "ext": "wav", + "files": os.path.basename(shot_wav), + "stagingDir": staging_dir + }] From 8f9e0c9d28d8aa5c5042d417a7841065f72650d7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Aug 2020 14:05:16 +0200 Subject: [PATCH 236/947] feat(sp): adding audio and shot instancing --- .../publish/integrate_hierarchy_ftrack.py | 2 +- pype/plugins/global/publish/extract_burnin.py | 3 + .../publish/extract_hierarchy_avalon.py | 2 +- pype/plugins/global/publish/extract_review.py | 3 + .../publish/collect_clip_instances.py | 179 +++++++++++++ .../publish/collect_clips.py | 242 ------------------ .../publish/collect_editorial.py | 1 + .../publish/collect_frame_ranges.py | 56 ---- .../publish/collect_hierarchy.py | 54 ++-- .../publish/collect_shots.py | 37 --- .../publish/extract_shot.py | 148 ----------- .../publish/extract_shot_data.py | 104 ++++++++ .../publish/validate_editorial_resources.py | 2 +- 13 files changed, 318 insertions(+), 515 deletions(-) create mode 100644 pype/plugins/standalonepublisher/publish/collect_clip_instances.py delete mode 100644 pype/plugins/standalonepublisher/publish/collect_clips.py delete mode 100644 pype/plugins/standalonepublisher/publish/collect_frame_ranges.py delete mode 100644 pype/plugins/standalonepublisher/publish/collect_shots.py delete mode 100644 pype/plugins/standalonepublisher/publish/extract_shot.py create mode 100644 pype/plugins/standalonepublisher/publish/extract_shot_data.py diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index a0059c55a6..cc569ce2d1 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -35,7 +35,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder - 0.04 label = 'Integrate Hierarchy To Ftrack' - families = ["clip", "shot"] + families = ["shot"] optional = False def process(self, context): diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index e1508b9131..08aa2c2d05 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -49,6 +49,9 @@ class ExtractBurnin(pype.api.Extractor): fields = None def process(self, instance): + representation = instance.data["representations"] + self.log.debug(f"_ representation: {representation}") + # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: instance_label = ( diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 83cf03b042..ab8226f6ef 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -7,7 +7,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): order = pyblish.api.ExtractorOrder - 0.01 label = "Extract Hierarchy To Avalon" - families = ["clip", "shot", "editorial"] + families = ["clip", "shot"] def process(self, context): if "hierarchyContext" not in context.data: diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index a16c3ce256..46de467b2c 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -50,6 +50,9 @@ class ExtractReview(pyblish.api.InstancePlugin): to_height = 1080 def process(self, instance): + representation = instance.data["representations"] + self.log.debug(f"_ representation: {representation}") + # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: instance_label = ( diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py new file mode 100644 index 0000000000..028a8b6aad --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -0,0 +1,179 @@ +import os +import opentimelineio as otio +import tempfile +import pyblish.api +from pype import lib as plib + + +class CollectClipInstances(pyblish.api.InstancePlugin): + """Collect Clips instances from editorial's OTIO sequence""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Collect Clips" + hosts = ["standalonepublisher"] + families = ["editorial"] + + # presets + subsets = { + "referenceMain": { + "family": "review", + "families": ["review", "ftrack"], + "ftrackFamily": "review", + "extension": ".mp4" + }, + "audioMain": { + "family": "audio", + "families": ["ftrack"], + "ftrackFamily": "audio", + "extension": ".wav" + }, + "shotMain": { + "family": "shot", + "families": [] + } + } + start_frame_offset = None # if 900000 for edl default then -900000 + custom_start_frame = None + + def process(self, instance): + + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + # get context + context = instance.context + + # create asset_names conversion table + if not context.data.get("assetsShared"): + self.log.debug("Created `assetsShared` in context") + context.data["assetsShared"] = dict() + + # get timeline otio data + timeline = instance.data["otio_timeline"] + fps = plib.get_asset()["data"]["fps"] + + tracks = timeline.each_child( + descended_from_type=otio.schema.track.Track + ) + self.log.debug(f"__ tracks: `{tracks}`") + + # get data from avalon + asset_entity = instance.context.data["assetEntity"] + asset_data = asset_entity["data"] + asset_name = asset_entity["name"] + self.log.debug(f"__ asset_entity: `{asset_entity}`") + + # Timeline data. + handle_start = int(asset_data["handleStart"]) + handle_end = int(asset_data["handleEnd"]) + + instances = [] + for track in tracks: + self.log.debug(f"__ track: `{track}`") + try: + track_start_frame = ( + abs(track.source_range.start_time.value) + ) + except AttributeError: + track_start_frame = 0 + + self.log.debug(f"__ track: `{track}`") + + for clip in track.each_child(): + # skip all generators like black ampty + if isinstance( + clip.media_reference, + otio.schema.GeneratorReference): + continue + + # Transitions are ignored, because Clips have the full frame + # range. + if isinstance(clip, otio.schema.transition.Transition): + continue + + if clip.name is None: + continue + + # basic unique asset name + clip_name = os.path.splitext(clip.name)[0].lower() + name = f"{asset_name.split('_')[0]}_{clip_name}" + + # frame ranges data + clip_in = clip.range_in_parent().start_time.value + clip_out = clip.range_in_parent().end_time_inclusive().value + clip_duration = clip.duration().value + + source_in = clip.trimmed_range().start_time.value + source_out = source_in + clip_duration + source_in_h = source_in - handle_start + source_out_h = source_out + handle_end + + clip_in_h = clip_in - handle_start + clip_out_h = clip_out + handle_end + + # define starting frame for future shot + frame_start = self.custom_start_frame or clip_in + + # add offset in case there is any + if self.start_frame_offset: + frame_start += self.start_frame_offset + + frame_end = frame_start + clip_duration + + # create shared new instance data + instance_data = { + "stagingDir": staging_dir, + + # shared attributes + "asset": name, + "assetShareName": name, + "editorialVideoPath": instance.data[ + "editorialVideoPath"], + "item": clip, + + # parent time properities + "trackStartFrame": track_start_frame, + "handleStart": handle_start, + "handleEnd": handle_end, + "fps": fps, + + # media source + "sourceIn": source_in, + "sourceOut": source_out, + "sourceInH": source_in_h, + "sourceOutH": source_out_h, + + # timeline + "clipIn": clip_in, + "clipOut": clip_out, + "clipDuration": clip_duration, + "clipInH": clip_in_h, + "clipOutH": clip_out_h, + "clipDurationH": clip_duration + handle_start + handle_end, + + # task + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartH": frame_start - handle_start, + "frameEndH": frame_end + handle_end + } + + # adding subsets to context as instances + for subset, properities in self.subsets.items(): + # adding Review-able instance + subset_instance_data = instance_data.copy() + subset_instance_data.update(properities) + subset_instance_data.update({ + # unique attributes + "name": f"{subset}_{name}", + "label": f"{subset} {name} ({clip_in}-{clip_out})", + "subset": subset + }) + instances.append(instance.context.create_instance( + **subset_instance_data)) + self.log.debug(instance_data) + + context.data["assetsShared"][name] = { + "_clipIn": clip_in, + "_clipOut": clip_out + } diff --git a/pype/plugins/standalonepublisher/publish/collect_clips.py b/pype/plugins/standalonepublisher/publish/collect_clips.py deleted file mode 100644 index 6a00675961..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_clips.py +++ /dev/null @@ -1,242 +0,0 @@ -import os - -import opentimelineio as otio -from bson import json_util - -import pyblish.api -from pype import lib as plib -from avalon import io - - -class CollectClips(pyblish.api.InstancePlugin): - """Collect Clips instances from editorial's OTIO sequence""" - - order = pyblish.api.CollectorOrder + 0.01 - label = "Collect Clips" - hosts = ["standalonepublisher"] - families = ["editorial"] - - def process(self, instance): - # get context - context = instance.context - - # create asset_names conversion table - if not context.data.get("assetsShared"): - self.log.debug("Created `assetsShared` in context") - context.data["assetsShared"] = dict() - - # get timeline otio data - timeline = instance.data["otio_timeline"] - fps = plib.get_asset()["data"]["fps"] - - tracks = timeline.each_child( - descended_from_type=otio.schema.track.Track - ) - self.log.debug(f"__ tracks: `{tracks}`") - - # get data from avalon - asset_entity = instance.context.data["assetEntity"] - asset_data = asset_entity["data"] - asset_name = asset_entity["name"] - self.log.debug(f"__ asset_entity: `{asset_entity}`") - - # split selected context asset name - asset_name = asset_name.split("_")[0] - - instances = [] - for track in tracks: - self.log.debug(f"__ track: `{track}`") - try: - track_start_frame = ( - abs(track.source_range.start_time.value) - ) - except AttributeError: - track_start_frame = 0 - - self.log.debug(f"__ track: `{track}`") - - for clip in track.each_child(): - # skip all generators like black ampty - if isinstance( - clip.media_reference, - otio.schema.GeneratorReference): - continue - - # Transitions are ignored, because Clips have the full frame - # range. - if isinstance(clip, otio.schema.transition.Transition): - continue - - if clip.name is None: - continue - - clip_name = os.path.splitext(clip.name)[0].lower() - name = f"{asset_name}_{clip_name}" - - source_in = clip.trimmed_range().start_time.value - clip_in = clip.range_in_parent().start_time.value - clip_out = clip.range_in_parent().end_time_inclusive().value - clip_duration = clip.duration().value - self.log.debug(f"__ source_in: `{source_in}`") - self.log.debug(f"__ clip_in: `{clip_in}`") - self.log.debug(f"__ clip_out: `{clip_out}`") - self.log.debug(f"__ clip_duration: `{clip_duration}`") - - label = f"{name} (framerange: {clip_in}-{clip_out})" - - new_instance_data = { - # shared attributes - "representations": [], - # timing properities - "trackStartFrame": track_start_frame, - "sourceIn": source_in, - "sourceOut": source_in + clip_duration, - "clipIn": clip_in, - "clipOut": clip_out, - "clipDuration": clip_duration, - "handleStart": int(asset_data["handleStart"]), - "handleEnd": int(asset_data["handleEnd"]), - "fps": fps - } - - # adding Review-able instance - shot_instance_data = new_instance_data.copy() - shot_instance_data.update({ - # unique attributes - "name": name, - "label": label, - "asset": name, - "subset": "plateRef", - "item": clip, - - # instance properities - "family": "clip", - "families": ["review", "ftrack"], - "ftrackFamily": "review", - "editorialVideoPath": instance.data[ - "editorialVideoPath"] - }) - instances.append( - instance.context.create_instance(**shot_instance_data) - ) - - context.data["assetsShared"][name] = { - "_clipIn": clip_in, - "_clipOut": clip_out - } - - # def process_old(self, instance): - # representation = instance.data["representations"][0] - # file_path = os.path.join( - # representation["stagingDir"], representation["files"] - # ) - # instance.context.data["editorialPath"] = file_path - # - # extension = os.path.splitext(file_path)[1][1:] - # kwargs = {} - # if extension == "edl": - # # EDL has no frame rate embedded so needs explicit frame rate else - # # 24 is asssumed. - # kwargs["rate"] = plib.get_asset()["data"]["fps"] - # - # timeline = otio.adapters.read_from_file(file_path, **kwargs) - # tracks = timeline.each_child( - # descended_from_type=otio.schema.track.Track - # ) - # asset_entity = instance.context.data["assetEntity"] - # asset_name = asset_entity["name"] - # - # # Ask user for sequence start. Usually 10:00:00:00. - # sequence_start_frame = 900000 - # - # # Project specific prefix naming. This needs to be replaced with some - # # options to be more flexible. - # asset_name = asset_name.split("_")[0] - # - # instances = [] - # for track in tracks: - # track_start_frame = ( - # abs(track.source_range.start_time.value) - sequence_start_frame - # ) - # for child in track.each_child(): - # # skip all generators like black ampty - # if isinstance( - # child.media_reference, - # otio.schema.GeneratorReference): - # continue - # - # # Transitions are ignored, because Clips have the full frame - # # range. - # if isinstance(child, otio.schema.transition.Transition): - # continue - # - # if child.name is None: - # continue - # - # # Hardcoded to expect a shot name of "[name].[extension]" - # child_name = os.path.splitext(child.name)[0].lower() - # name = f"{asset_name}_{child_name}" - # - # frame_start = track_start_frame - # frame_start += child.range_in_parent().start_time.value - # frame_end = track_start_frame - # frame_end += child.range_in_parent().end_time_inclusive().value - # - # label = f"{name} (framerange: {frame_start}-{frame_end})" - # instances.append( - # instance.context.create_instance(**{ - # "name": name, - # "label": label, - # "frameStart": frame_start, - # "frameEnd": frame_end, - # "family": "shot", - # "families": ["review", "ftrack"], - # "ftrackFamily": "review", - # "asset": name, - # "subset": "shotMain", - # "representations": [], - # "source": file_path - # }) - # ) - # - # visual_hierarchy = [asset_entity] - # while True: - # visual_parent = io.find_one( - # {"_id": visual_hierarchy[-1]["data"]["visualParent"]} - # ) - # if visual_parent: - # visual_hierarchy.append(visual_parent) - # else: - # visual_hierarchy.append(instance.context.data["projectEntity"]) - # break - # - # context_hierarchy = None - # for entity in visual_hierarchy: - # childs = {} - # if context_hierarchy: - # name = context_hierarchy.pop("name") - # childs = {name: context_hierarchy} - # else: - # for instance in instances: - # childs[instance.data["name"]] = { - # "childs": {}, - # "entity_type": "Shot", - # "custom_attributes": { - # "frameStart": instance.data["frameStart"], - # "frameEnd": instance.data["frameEnd"] - # } - # } - # - # context_hierarchy = { - # "entity_type": entity["data"]["entityType"], - # "childs": childs, - # "name": entity["name"] - # } - # - # name = context_hierarchy.pop("name") - # context_hierarchy = {name: context_hierarchy} - # instance.context.data["hierarchyContext"] = context_hierarchy - # self.log.info( - # "Hierarchy:\n" + - # json_util.dumps(context_hierarchy, sort_keys=True, indent=4) - # ) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index fa5b5f13a3..406848628a 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -62,6 +62,7 @@ class CollectEditorial(pyblish.api.InstancePlugin): ) self.log.debug(f"__ video_path: `{video_path}`") instance.data["editorialVideoPath"] = video_path + instance.data["stagingDir"] = staging_dir # get editorial sequence file into otio timeline object extension = os.path.splitext(file_path)[1] diff --git a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py b/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py deleted file mode 100644 index 5e8292458f..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_frame_ranges.py +++ /dev/null @@ -1,56 +0,0 @@ -import pyblish.api - - -class CollectClipFrameRanges(pyblish.api.InstancePlugin): - """Collect all frame range data""" - - order = pyblish.api.CollectorOrder + 0.101 - label = "Collect Frame Ranges" - hosts = ["standalonepublisher"] - families = ["clip"] - - # presets - start_frame_offset = None # if 900000 for edl default then -900000 - custom_start_frame = None - - def process(self, instance): - - data = dict() - - # Timeline data. - handle_start = instance.data["handleStart"] - handle_end = instance.data["handleEnd"] - - source_in_h = instance.data("sourceInH", - instance.data("sourceIn") - handle_start) - source_out_h = instance.data("sourceOutH", - instance.data("sourceOut") + handle_end) - - timeline_in = instance.data["clipIn"] - timeline_out = instance.data["clipOut"] - - timeline_in_h = timeline_in - handle_start - timeline_out_h = timeline_out + handle_end - - # define starting frame for future shot - frame_start = self.custom_start_frame or timeline_in - - # add offset in case there is any - if self.start_frame_offset: - frame_start += self.start_frame_offset - - frame_end = frame_start + (timeline_out - timeline_in) - - data.update({ - "sourceInH": source_in_h, - "sourceOutH": source_out_h, - "frameStart": frame_start, - "frameEnd": frame_end, - "clipInH": timeline_in_h, - "clipOutH": timeline_out_h, - "clipDurationH": instance.data.get( - "clipDuration") + handle_start + handle_end - } - ) - self.log.debug("__ data: {}".format(data)) - instance.data.update(data) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 87878d560e..8380706457 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -16,7 +16,7 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): label = "Collect Hierarchy Clip" order = pyblish.api.CollectorOrder + 0.101 hosts = ["standalonepublisher"] - families = ["clip"] + families = ["shot"] # presets shot_rename_template = None @@ -141,7 +141,6 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): # dealing with shared attributes across instances # with the same asset name - if assets_shared.get(asset): self.log.debug("Adding to shared assets: `{}`".format( asset)) @@ -153,7 +152,6 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): "asset": instance.data["asset"], "hierarchy": instance.data["hierarchy"], "parents": instance.data["parents"], - "fps": instance.data["fps"], "tasks": instance.data["tasks"] }) @@ -185,35 +183,22 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): final_context = {} for instance in instances: - if 'clip' not in instance.data.get('family', ''): + if 'editorial' in instance.data.get('family', ''): continue - - name = instance.data["asset"] - - # get handles - handle_start = int(instance.data["handleStart"]) - handle_end = int(instance.data["handleEnd"]) - - # inject assetsShared to other plates types + # inject assetsShared to other instances with + # the same `assetShareName` attribute in data assets_shared = context.data.get("assetsShared") + asset_shared_name = instance.data.get("assetShareName") + self.log.debug(f"_ assets_shared: {assets_shared}") + self.log.debug(f"_ asset_shared_name: {asset_shared_name}") - if assets_shared: - s_asset_data = assets_shared.get(name) - if s_asset_data: - self.log.debug("__ s_asset_data: {}".format(s_asset_data)) - name = instance.data["asset"] = s_asset_data["asset"] - instance.data["parents"] = s_asset_data["parents"] - instance.data["hierarchy"] = s_asset_data["hierarchy"] - instance.data["tasks"] = s_asset_data["tasks"] - instance.data["fps"] = s_asset_data["fps"] - - # adding frame start if any on instance - start_frame = s_asset_data.get("startingFrame") - if start_frame: - instance.data["frameStart"] = start_frame - instance.data["frameEnd"] = start_frame + ( - instance.data["clipOut"] - - instance.data["clipIn"]) + s_asset_data = assets_shared.get(asset_shared_name) + if s_asset_data: + self.log.debug("__ s_asset_data: {}".format(s_asset_data)) + instance.data["asset"] = s_asset_data["asset"] + instance.data["parents"] = s_asset_data["parents"] + instance.data["hierarchy"] = s_asset_data["hierarchy"] + instance.data["tasks"] = s_asset_data["tasks"] self.log.debug( "__ instance.data[parents]: {}".format( @@ -229,6 +214,17 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): "__ instance.data[name]: {}".format(instance.data["name"]) ) + # generate hierarchy data only on shot instances + if 'shot' not in instance.data.get('family', ''): + continue + + name = instance.data["asset"] + + # get handles + handle_start = int(instance.data["handleStart"]) + handle_end = int(instance.data["handleEnd"]) + + in_info = {} # suppose that all instances are Shots diff --git a/pype/plugins/standalonepublisher/publish/collect_shots.py b/pype/plugins/standalonepublisher/publish/collect_shots.py deleted file mode 100644 index 269a65fd86..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_shots.py +++ /dev/null @@ -1,37 +0,0 @@ -from pyblish import api - - -class CollectShots(api.InstancePlugin): - """Collect Shot from Clip.""" - - # Run just before CollectClipSubsets - order = api.CollectorOrder + 0.1021 - label = "Collect Shots" - hosts = ["standalonepublisher"] - families = ["clip"] - - def process(self, instance): - - # Collect data. - data = {} - for key, value in instance.data.items(): - data[key] = value - - data["family"] = "shot" - data["families"] = [] - - data["subset"] = data["family"] + "Main" - - data["name"] = data["subset"] + "_" + data["asset"] - - data["label"] = ( - "{} - {} - tasks:{}".format( - data["asset"], - data["subset"], - data["tasks"] - ) - ) - - # Create instance. - self.log.debug("Creating instance with: {}".format(data["name"])) - instance.context.create_instance(**data) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot.py b/pype/plugins/standalonepublisher/publish/extract_shot.py deleted file mode 100644 index 7c1486d7b6..0000000000 --- a/pype/plugins/standalonepublisher/publish/extract_shot.py +++ /dev/null @@ -1,148 +0,0 @@ -import os -import clique -import pype.api -import pype.lib as plib - -from pprint import pformat - - -class ExtractShot(pype.api.Extractor): - """Extract shot "mov" and "wav" files.""" - - label = "Extract Shot" - hosts = ["standalonepublisher"] - families = ["clip"] - - # presets - add_representation = None # ".jpeg" - add_audio = True - - def process(self, instance): - # get ffmpet path - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") - - # get staging dir - staging_dir = self.staging_dir(instance) - self.log.info("Staging dir set to: `{}`".format(staging_dir)) - - # Generate mov file. - fps = instance.data["fps"] - video_file_path = instance.data["editorialVideoPath"] - ext = os.path.splitext(os.path.basename(video_file_path))[-1] - - clip_trimed_path = os.path.join( - staging_dir, instance.data["name"] + ext) - - # check video file metadata - input_data = plib.ffprobe_streams(video_file_path)[0] - self.log.debug(f"__ input_data: `{input_data}`") - - args = [ - ffmpeg_path, - "-ss", str(instance.data["clipIn"] / fps), - "-i", video_file_path, - "-t", str( - (instance.data["clipOut"] - instance.data["clipIn"] + 1) / - fps - ), - "-crf", "18", - "-pix_fmt", "yuv420p", - clip_trimed_path - ] - self.log.info(f"Processing: {args}") - ffmpeg_args = " ".join(args) - output = pype.api.subprocess(ffmpeg_args) - self.log.info(output) - - instance.data["families"].remove("review") - instance.data["families"].append("clip") - instance.data["family"] = "review" - - # frame ranges - frame_start = int(instance.data["frameStart"]) - frame_end = int(instance.data["frameEnd"]) - handle_start = int(instance.data["handleStart"]) - handle_end = int(instance.data["handleEnd"]) - - instance.data["representations"].append({ - "name": ext[1:], - "ext": ext[1:], - "files": os.path.basename(clip_trimed_path), - "stagingDir": staging_dir, - "frameStart": frame_start, - "frameEnd": frame_end, - "frameStartFtrack": frame_start - handle_start, - "frameEndFtrack": frame_end - handle_end, - "fps": fps, - "thumbnail": True, - "tags": ["review", "ftrackreview", "delete"] - }) - - self.log.debug(f"Instance data: {pformat(instance.data)}") - - if self.add_representation: - # Generate jpegs. - clip_img_sequence = os.path.join( - staging_dir, instance.data["name"] + ".%04d.jpeg" - ) - args = [ffmpeg_path, "-i", clip_trimed_path, clip_img_sequence] - self.log.info(f"Processing: {args}") - output = pype.lib._subprocess(args) - self.log.info(output) - - # collect jpeg sequence if editorial data for publish - # are image sequence - collection = clique.Collection( - head=instance.data["name"] + ".", tail='.jpeg', padding=4 - ) - for f in os.listdir(staging_dir): - if collection.match(f): - collection.add(f) - - instance.data["representations"].append({ - "name": "jpeg", - "ext": "jpeg", - "files": list(collection), - "stagingDir": staging_dir - }) - - if self.add_audio: - audio_ext = ".wav" - # Generate wav file. - shot_wav = os.path.join( - staging_dir, instance.data["name"] + audio_ext) - # Collect data. - data = {} - for key, value in instance.data.items(): - data[key] = value - - data["family"] = "audio" - data["families"] = ["ftrack"] - - data["subset"] = "audioMain" - - data["source"] = shot_wav - - data["name"] = data["subset"] + "_" + data["asset"] - - data["label"] = "{} - {} - ({})".format( - data['asset'], - data["subset"], - audio_ext - ) - - # Create instance. - self.log.debug("Creating instance with: {}".format(data["name"])) - instance = instance.context.create_instance(**data) - - args = [ffmpeg_path, "-i", clip_trimed_path, shot_wav] - self.log.info(f"Processing: {args}") - output = pype.lib._subprocess(args) - self.log.info(output) - - instance.data["representations"] = [{ - "name": "wav", - "ext": "wav", - "files": os.path.basename(shot_wav), - "stagingDir": staging_dir - }] diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py new file mode 100644 index 0000000000..2732d0fa59 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -0,0 +1,104 @@ +import os +import clique +import pype.api +import pype.lib as plib + +from pprint import pformat + + +class ExtractShotData(pype.api.Extractor): + """Extract shot "mov" and "wav" files.""" + + label = "Extract Shot Data" + hosts = ["standalonepublisher"] + families = ["review", "audio"] + + # presets + add_representation = None # ".jpeg" + + def process(self, instance): + representation = instance.data.get("representations") + self.log.debug(f"_ representation: {representation}") + + if not representation: + instance.data["representations"] = list() + + # get ffmpet path + ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + + # get staging dir + staging_dir = self.staging_dir(instance) + self.log.info("Staging dir set to: `{}`".format(staging_dir)) + + # Generate mov file. + fps = instance.data["fps"] + video_file_path = instance.data["editorialVideoPath"] + ext = instance.data.get("extension", ".mov") + + clip_trimed_path = os.path.join( + staging_dir, instance.data["name"] + ext) + # + # # check video file metadata + # input_data = plib.ffprobe_streams(video_file_path)[0] + # self.log.debug(f"__ input_data: `{input_data}`") + + args = [ + ffmpeg_path, + "-ss", str(instance.data["clipInH"] / fps), + "-i", video_file_path, + "-t", str(instance.data["clipDurationH"] / fps), + "-crf", "18", + "-pix_fmt", "yuv420p", + clip_trimed_path + ] + self.log.info(f"Processing: {args}") + ffmpeg_args = " ".join(args) + output = pype.api.subprocess(ffmpeg_args) + self.log.info(output) + + repr = { + "name": ext[1:], + "ext": ext[1:], + "files": os.path.basename(clip_trimed_path), + "stagingDir": staging_dir, + "frameStart": int(instance.data["frameStart"]), + "frameEnd": int(instance.data["frameEnd"]), + "frameStartFtrack": int(instance.data["frameStartH"]), + "frameEndFtrack": int(instance.data["frameEndH"]), + "fps": fps, + } + + if ext[1:] in ["mov", "mp4"]: + repr.update({ + "thumbnail": True, + "tags": ["review", "ftrackreview", "delete"]}) + + instance.data["representations"].append(repr) + + if self.add_representation: + # Generate jpegs. + clip_img_sequence = os.path.join( + staging_dir, instance.data["name"] + ".%04d.jpeg" + ) + args = [ffmpeg_path, "-i", clip_trimed_path, clip_img_sequence] + self.log.info(f"Processing: {args}") + output = pype.lib._subprocess(args) + self.log.info(output) + + # collect jpeg sequence if editorial data for publish + # are image sequence + collection = clique.Collection( + head=instance.data["name"] + ".", tail='.jpeg', padding=4 + ) + for f in os.listdir(staging_dir): + if collection.match(f): + collection.add(f) + + instance.data["representations"].append({ + "name": "jpeg", + "ext": "jpeg", + "files": list(collection), + "stagingDir": staging_dir + }) + + self.log.debug(f"Instance data: {pformat(instance.data)}") diff --git a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py index 6033ed919b..ebc449c4ec 100644 --- a/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py +++ b/pype/plugins/standalonepublisher/publish/validate_editorial_resources.py @@ -9,7 +9,7 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): label = "Validate Editorial Resources" hosts = ["standalonepublisher"] - families = ["clip"] + families = ["audio", "review"] order = pype.api.ValidateContentsOrder def process(self, instance): From ef2e4d507d783758a8edf388b2454d0fff68cc8f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 5 Aug 2020 16:46:12 +0200 Subject: [PATCH 237/947] fix(sp): hierarchy was not generating proper parents --- .../publish/collect_hierarchy.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 8380706457..8e48d141f2 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -88,21 +88,22 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): if self.shot_add_hierarchy: # fill the parents parts from presets - for parent in self.shot_add_hierarchy["parents"]: - if not self.shot_add_hierarchy["parents"][parent]: - prnt = {"entity"} - else: - self.shot_add_hierarchy["parents"][parent] = self.shot_add_hierarchy[ - "parents"][parent].format(**self.hierarchy_data) - prnt = self.convert_to_entity( - parent, self.shot_add_hierarchy["parents"][parent]) + shot_add_hierarchy = self.shot_add_hierarchy.copy() + hierarchy_parents = shot_add_hierarchy["parents"].copy() + for parent in hierarchy_parents: + hierarchy_parents[parent] = hierarchy_parents[parent].format( + **self.hierarchy_data) + prnt = self.convert_to_entity( + parent, hierarchy_parents[parent]) parents.append(prnt) - hierarchy = self.shot_add_hierarchy[ - "parents_path"].format(**self.shot_add_hierarchy["parents"]) + hierarchy = shot_add_hierarchy[ + "parents_path"].format(**hierarchy_parents) instance.data["hierarchy"] = hierarchy instance.data["parents"] = parents + self.log.debug(f"_>_ hierarchy: {hierarchy}") + self.log.debug(f"_>_ parents: {parents}") if self.shot_add_tasks: instance.data["tasks"] = self.shot_add_tasks @@ -180,14 +181,13 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): def process(self, context): instances = context # create hierarchyContext attr if context has none - + assets_shared = context.data.get("assetsShared") final_context = {} for instance in instances: if 'editorial' in instance.data.get('family', ''): continue # inject assetsShared to other instances with # the same `assetShareName` attribute in data - assets_shared = context.data.get("assetsShared") asset_shared_name = instance.data.get("assetShareName") self.log.debug(f"_ assets_shared: {assets_shared}") self.log.debug(f"_ asset_shared_name: {asset_shared_name}") From 7f156a957e9eabce4d214a573cdb55bf0303796e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 10:47:09 +0200 Subject: [PATCH 238/947] input have is_modified property methods and was_overriden attribute --- pype/tools/config_setting/widgets/inputs.py | 65 ++++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 50bd3d58f4..5fadf2ee60 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -40,7 +40,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_group = True self.is_group = is_group - self.is_modified = False + self._is_modified = False + self.was_overriden = False self._is_overriden = False self._state = None @@ -96,6 +97,10 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -107,7 +112,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True @@ -161,7 +166,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_group = True self.is_group = is_group - self.is_modified = False + self._is_modified = False + self.was_overriden = False self._is_overriden = False self._state = None @@ -204,6 +210,10 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -227,7 +237,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.origin_value) def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True @@ -282,7 +292,8 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_group = True self.is_group = is_group - self.is_modified = False + self._is_modified = False + self.was_overriden = False self._is_overriden = False self._state = None @@ -335,6 +346,10 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -358,7 +373,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(0) def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True @@ -413,7 +428,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_group = True self.is_group = is_group - self.is_modified = False + self._is_modified = False + self.was_overriden = False self._is_overriden = False self._state = None @@ -456,6 +472,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -479,7 +499,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value("") def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True @@ -535,6 +555,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False + self.was_overriden = False self._is_overriden = False self._state = None @@ -575,6 +596,10 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -598,7 +623,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value("") def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True @@ -653,7 +678,8 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_group = True self.is_group = is_group - self.is_modified = False + self._is_modified = False + self.was_overriden = False self._is_overriden = False self._state = None @@ -702,6 +728,10 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -740,7 +770,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_value_change(self, item=None): value = self.item_value() - self.is_modified = value != self.origin_value + self._is_modified = value != self.origin_value if self.is_overidable: self._is_overriden = True @@ -961,7 +991,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): if not any_parent_is_group and not is_group: is_group = True - self.is_modified = False + self._is_modified = False self.is_group = is_group self._is_overriden = False @@ -1005,6 +1035,10 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def child_overriden(self): return self._is_overriden + @property + def is_modified(self): + return self._is_modified + @property def is_overidable(self): return self._parent.is_overidable @@ -1016,7 +1050,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def _on_value_change(self, item=None): - self.is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True @@ -1713,6 +1747,8 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def update_style(self, is_overriden=None): child_modified = self.child_modified if is_overriden is None: + is_overriden = self.is_overriden + child_overriden = self.child_overriden child_state = self.style_state(child_overriden, child_modified) if child_state != self._child_state: @@ -1720,9 +1756,6 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.style().polish(self) self._child_state = child_state - if is_overriden is None: - is_overriden = self.is_overriden - if child_modified and not is_overriden: state = self.default_state else: From 3770d642e92568515fe1c9cfb6790172c3a6e6a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 10:57:49 +0200 Subject: [PATCH 239/947] use only keys in delivery with `{root` in value --- pype/modules/ftrack/actions/action_delivery.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 06257f32d5..ce02f2054d 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -81,13 +81,15 @@ class Delivery(BaseAction): anatomy = Anatomy(project_name) new_anatomies = [] first = None - for key in (anatomy.templates.get("delivery") or {}): - new_anatomies.append({ - "label": key, - "value": key - }) - if first is None: - first = key + for key, template in (anatomy.templates.get("delivery") or {}).items(): + # Use only keys with `{root}` or `{root[*]}` in value + if "{root" in template: + new_anatomies.append({ + "label": key, + "value": key + }) + if first is None: + first = key skipped = False # Add message if there are any common components From def7fa7e08447a48cf76d3943b73e6c01121ba8e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 11:06:16 +0200 Subject: [PATCH 240/947] added check for template value that it is a string --- pype/modules/ftrack/actions/action_delivery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index ce02f2054d..231aebdf7a 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -83,7 +83,7 @@ class Delivery(BaseAction): first = None for key, template in (anatomy.templates.get("delivery") or {}).items(): # Use only keys with `{root}` or `{root[*]}` in value - if "{root" in template: + if isinstance(template, str) and "{root" in template: new_anatomies.append({ "label": key, "value": key From fde11ec034de0d9f414119c878dcb007f7603b9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 11:15:05 +0200 Subject: [PATCH 241/947] is_modified also looks if overridatio nhas changed --- pype/tools/config_setting/widgets/inputs.py | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 5fadf2ee60..052520d8d5 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -41,7 +41,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self._is_modified = False - self.was_overriden = False + self._was_overriden = False self._is_overriden = False self._state = None @@ -99,7 +99,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -167,7 +167,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self._is_modified = False - self.was_overriden = False + self._was_overriden = False self._is_overriden = False self._state = None @@ -212,7 +212,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -293,7 +293,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self._is_modified = False - self.was_overriden = False + self._was_overriden = False self._is_overriden = False self._state = None @@ -348,7 +348,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -429,7 +429,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self._is_modified = False - self.was_overriden = False + self._was_overriden = False self._is_overriden = False self._state = None @@ -474,7 +474,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -555,7 +555,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self.is_modified = False - self.was_overriden = False + self._was_overriden = False self._is_overriden = False self._state = None @@ -598,7 +598,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -679,7 +679,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_group = is_group self._is_modified = False - self.was_overriden = False + self._was_overriden = False self._is_overriden = False self._state = None @@ -730,7 +730,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -993,6 +993,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._is_modified = False self.is_group = is_group + self._was_overriden = False self._is_overriden = False self._state = None @@ -1037,7 +1038,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): From 2a4a81bde19fe47a01d03c79af9878a6d170c86d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 14:45:27 +0200 Subject: [PATCH 242/947] raw json input has special input widget --- pype/tools/config_setting/style/style.css | 2 +- pype/tools/config_setting/widgets/inputs.py | 90 ++++++++++++++------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css index adf8344f89..a0c5db86a1 100644 --- a/pype/tools/config_setting/style/style.css +++ b/pype/tools/config_setting/style/style.css @@ -57,7 +57,7 @@ QPushButton[btn-type="expand-toggle"] { background: transparent; } -#RawJson[state="invalid"] { +#RawJsonInput[state="invalid"] { border-color: #ff5511; } diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 052520d8d5..bd7d4b501f 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -657,6 +657,63 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: self.item_value()} +class RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + self.is_valid = None + + def set_value(self, value): + self.setPlainText(value) + + def setPlainText(self, *args, **kwargs): + super(RawJsonInput, self).setPlainText(*args, **kwargs) + self.validate() + + def focusOutEvent(self, event): + super(RawJsonInput, self).focusOutEvent(event) + self.validate() + + def validate_value(self, value): + if isinstance(value, str) and not value: + return True + + try: + json.loads(value) + return True + except Exception: + return False + + def update_style(self, is_valid=None): + if is_valid is None: + return self.validate() + + if is_valid != self.is_valid: + self.is_valid = is_valid + if is_valid: + state = "" + else: + state = "invalid" + self.setProperty("state", state) + self.style().polish(self) + + def value(self): + return self.toPlainText() + + def validate(self): + value = self.value() + is_valid = self.validate_value(value) + self.update_style(is_valid) + + class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) @@ -684,21 +741,14 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._state = None - self.is_valid = None - super(RawJsonWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) - self.text_input = QtWidgets.QPlainTextEdit() - self.text_input.setObjectName("RawJson") - self.text_input.setTabStopDistance( - QtGui.QFontMetricsF( - self.text_input.font() - ).horizontalAdvance(" ") * 4 - ) + self.text_input = RawJsonInput() + if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) @@ -742,20 +792,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def validate_value(self, value): - if not value: - return True - try: - json.dumps(value) - return True - except Exception: - return False - def set_value(self, value, origin_value=False): - is_valid = self.validate_value(value) - if is_valid: - value = json.dumps(value, indent=4) - self.text_input.setPlainText(value) if origin_value: @@ -774,15 +811,6 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): if self.is_overidable: self._is_overriden = True - is_valid = self.validate_value(value) - if is_valid != self.is_valid: - self.is_valid = is_valid - if is_valid: - state = "" - else: - state = "invalid" - self.text_input.setProperty("state", state) - self.text_input.style().polish(self.text_input) self.update_style() self.value_changed.emit(self) From fb2681f98618b41a54e6dabc78364acf7b6a391c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 15:53:55 +0200 Subject: [PATCH 243/947] renamed origin_value to default_value --- pype/tools/config_setting/widgets/inputs.py | 106 ++++++++++---------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index bd7d4b501f..e3e60612cc 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -73,22 +73,24 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.checkbox.setChecked(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() self.checkbox.stateChanged.connect(self._on_value_change) - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.checkbox.setChecked(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.reset_value() + def apply_overrides(self, overrides): + @property def child_modified(self): return self.is_modified @@ -112,7 +114,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -198,7 +200,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.int_input.setValue(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() self.int_input.valueChanged.connect(self._on_value_change) @@ -224,20 +226,20 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.int_input.setValue(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def clear_value(self): self.set_value(0) def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -334,7 +336,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.float_input.setValue(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() self.float_input.valueChanged.connect(self._on_value_change) @@ -360,20 +362,20 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.float_input.setValue(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.set_value(0) def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -460,7 +462,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.text_input.setText(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() self.text_input.textChanged.connect(self._on_value_change) @@ -486,20 +488,20 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.text_input.setText(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.set_value("") def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -584,7 +586,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.text_input.setPlainText(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() self.text_input.textChanged.connect(self._on_value_change) @@ -610,20 +612,20 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.text_input.setPlainText(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.set_value("") def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -766,7 +768,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.text_input.setPlainText(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() self.text_input.textChanged.connect(self._on_value_change) @@ -792,22 +794,22 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.text_input.setPlainText(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.set_value("") def _on_value_change(self, item=None): value = self.item_value() - self._is_modified = value != self.origin_value + self._is_modified = value != self.default_value if self.is_overidable: self._is_overriden = True @@ -915,21 +917,21 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: self.set_value(value) - self.origin_value = self.item_value() + self.default_value = self.item_value() - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): for input_field in self.input_fields: self.remove_row(input_field) for item_text in value: self.add_row(text=item_text) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.set_value([]) @@ -1054,7 +1056,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): layout.addWidget(self.value_widget) self.setLayout(layout) - self.origin_value = self.item_value() + self.default_value = self.item_value() @property def child_modified(self): @@ -1079,7 +1081,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.origin_value + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -1087,14 +1089,14 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def set_value(self, value, origin_value=False): + def set_value(self, value, default_value=False): self.value_widget.set_value(value) - if origin_value: - self.origin_value = self.item_value() + if default_value: + self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.origin_value) + self.set_value(self.default_value) def clear_value(self): self.set_value([]) @@ -1533,7 +1535,7 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): self.value_input.value_changed.connect(self._on_value_change) self.origin_key = self._key() - self.origin_value = self.value_input.item_value() + self.default_value = self.value_input.item_value() self.is_single = False @@ -1630,7 +1632,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): if self.count() == 0: self.add_row() - self.origin_value = self.config_value() + self.default_value = self.config_value() @property def is_overidable(self): @@ -1680,7 +1682,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not None and key is not None: item_widget.origin_key = key item_widget.key_input.setText(key) - item_widget.value_input.set_value(value, origin_value=True) + item_widget.value_input.set_value(value, default_value=True) else: self._on_value_change() self.parent().updateGeometry() @@ -1750,10 +1752,10 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.key = input_data["key"] - self.origin_value = self.item_value() + self.default_value = self.item_value() def _on_value_change(self, item=None): - self.child_modified = self.item_value() != self.origin_value + self.child_modified = self.item_value() != self.default_value if self.is_group and self.is_overidable: self._is_overriden = True From a59aa34a8ab27b202c377c2134db3a9b17bbfda0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 16:04:39 +0200 Subject: [PATCH 244/947] just added override_value attribute --- pype/tools/config_setting/widgets/inputs.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index e3e60612cc..5f0411b72b 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -74,6 +74,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.checkbox.setChecked(value) self.default_value = self.item_value() + self.override_value = None self.checkbox.stateChanged.connect(self._on_value_change) @@ -201,6 +202,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.int_input.setValue(value) self.default_value = self.item_value() + self.override_value = None self.int_input.valueChanged.connect(self._on_value_change) @@ -337,6 +339,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.float_input.setValue(value) self.default_value = self.item_value() + self.override_value = None self.float_input.valueChanged.connect(self._on_value_change) @@ -463,6 +466,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.text_input.setText(value) self.default_value = self.item_value() + self.override_value = None self.text_input.textChanged.connect(self._on_value_change) @@ -587,6 +591,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.text_input.setPlainText(value) self.default_value = self.item_value() + self.override_value = None self.text_input.textChanged.connect(self._on_value_change) @@ -769,6 +774,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.text_input.setPlainText(value) self.default_value = self.item_value() + self.override_value = None self.text_input.textChanged.connect(self._on_value_change) @@ -918,6 +924,7 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(value) self.default_value = self.item_value() + self.override_value = None def set_value(self, value, default_value=False): for input_field in self.input_fields: @@ -1057,6 +1064,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.setLayout(layout) self.default_value = self.item_value() + self.override_value = None @property def child_modified(self): @@ -1534,9 +1542,12 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): self.key_input.textChanged.connect(self._on_value_change) self.value_input.value_changed.connect(self._on_value_change) - self.origin_key = self._key() + self.default_key = self._key() self.default_value = self.value_input.item_value() + self.override_key = None + self.override_value = None + self.is_single = False def _key(self): @@ -1563,7 +1574,7 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def is_key_modified(self): - return self._key() != self.origin_key + return self._key() != self.default_key def is_value_modified(self): return self.value_input.is_modified @@ -1633,6 +1644,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.add_row() self.default_value = self.config_value() + self.override_value = None @property def is_overidable(self): @@ -1680,7 +1692,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: - item_widget.origin_key = key + item_widget.default_key = key item_widget.key_input.setText(key) item_widget.value_input.set_value(value, default_value=True) else: @@ -1753,6 +1765,7 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.key = input_data["key"] self.default_value = self.item_value() + self.override_value = None def _on_value_change(self, item=None): self.child_modified = self.item_value() != self.default_value From 407fbbe77babb1fddf7fec433152d7c2b3cf8831 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 16:57:09 +0200 Subject: [PATCH 245/947] call aplpy_overrides on project change --- pype/tools/config_setting/widgets/base.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 52184b4c8a..a90eefd400 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -345,9 +345,17 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.content_layout.addWidget(item) def _on_project_change(self): - self.is_overidable = ( - self.project_list_widget.project_name() is not None - ) + project_name = self.project_list_widget.project_name() + + if project_name is None: + overrides = None + self.is_overidable = False + else: + overrides = config.project_preset_overrides(project_name) + self.is_overidable = True + + for item in self.input_fields: + item.apply_overrides(overrides) def _save(self): output = {} From 2e7980104f1cee00828455148e48250a8fc7c04b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 16:57:54 +0200 Subject: [PATCH 246/947] addde apply_overrides to inputs --- pype/tools/config_setting/widgets/inputs.py | 76 +++++++++++++++++---- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 5f0411b72b..ea6a9e3483 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -78,19 +78,30 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.checkbox.stateChanged.connect(self._on_value_change) - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.checkbox.setChecked(value) if default_value: self.default_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + if self.is_overidable: + self.set_value(self.override_value) + else: + self.set_value(self.default_value) def clear_value(self): self.reset_value() - def apply_overrides(self, overrides): + def apply_overrides(self, override_value): + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.checkbox.setChecked(value) @property def child_modified(self): @@ -115,9 +126,17 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.default_value + _value = self.item_value() + is_modified = None if self.is_overidable: self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value + + if is_modified is None: + is_modified = _value != self.default_value + + self._is_modified = is_modified self.update_style() @@ -228,7 +247,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.int_input.setValue(value) if default_value: self.default_value = self.item_value() @@ -240,6 +259,16 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset_value(self): self.set_value(self.default_value) + def apply_overrides(self, override_value): + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.int_input.setValue(value) + def _on_value_change(self, item=None): self._is_modified = self.item_value() != self.default_value if self.is_overidable: @@ -365,7 +394,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.float_input.setValue(value) if default_value: self.default_value = self.item_value() @@ -374,6 +403,16 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset_value(self): self.set_value(self.default_value) + def apply_overrides(self, override_value): + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.float_input.setChecked(value) + def clear_value(self): self.set_value(0) @@ -492,7 +531,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.text_input.setText(value) if default_value: self.default_value = self.item_value() @@ -501,6 +540,9 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset_value(self): self.set_value(self.default_value) + def apply_overrides(self, override_value): + self.set_value(override_value, override_value=True) + def clear_value(self): self.set_value("") @@ -617,7 +659,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: self.default_value = self.item_value() @@ -626,6 +668,9 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset_value(self): self.set_value(self.default_value) + def apply_overrides(self, override_value): + self.set_value(override_value, override_value=True) + def clear_value(self): self.set_value("") @@ -678,7 +723,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.is_valid = None - def set_value(self, value): + def set_value(self, value, *, default_value=False): self.setPlainText(value) def setPlainText(self, *args, **kwargs): @@ -800,9 +845,8 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) - if default_value: self.default_value = self.item_value() self._on_value_change() @@ -813,6 +857,9 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value("") + def apply_overrides(self, override_value): + self.set_value(override_value, override_value=True) + def _on_value_change(self, item=None): value = self.item_value() self._is_modified = value != self.default_value @@ -926,7 +973,7 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.default_value = self.item_value() self.override_value = None - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): for input_field in self.input_fields: self.remove_row(input_field) @@ -1097,7 +1144,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def set_value(self, value, default_value=False): + def set_value(self, value, *, default_value=False): self.value_widget.set_value(value) if default_value: self.default_value = self.item_value() @@ -1109,6 +1156,9 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def clear_value(self): self.set_value([]) + def apply_overrides(self, override_value): + self.set_value(override_value, override_value=True) + def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden From 04f650b9b59637adc74e3b6e9a67eff2f142a5e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 18:12:01 +0200 Subject: [PATCH 247/947] project overrides are appliable for boolean --- pype/tools/config_setting/widgets/inputs.py | 98 ++++++++++++++++++--- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index ea6a9e3483..53bb2d729d 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -95,13 +95,16 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self.override_value = override_value + self._is_modified = False if override_value is None: self._is_overriden = False + self._was_overriden = False value = self.default_value else: self._is_overriden = True + self._was_overriden = True value = override_value - self.checkbox.setChecked(value) + self.set_value(value) @property def child_modified(self): @@ -113,7 +116,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self._was_overriden != self._is_overriden) @property def is_overidable(self): @@ -267,7 +270,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: self._is_overriden = True value = override_value - self.int_input.setValue(value) + self.set_value(value) def _on_value_change(self, item=None): self._is_modified = self.item_value() != self.default_value @@ -411,7 +414,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: self._is_overriden = True value = override_value - self.float_input.setChecked(value) + self.set_value(value) def clear_value(self): self.set_value(0) @@ -541,7 +544,14 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.default_value) def apply_overrides(self, override_value): - self.set_value(override_value, override_value=True) + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.set_value(value) def clear_value(self): self.set_value("") @@ -669,7 +679,14 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.default_value) def apply_overrides(self, override_value): - self.set_value(override_value, override_value=True) + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.set_value(value) def clear_value(self): self.set_value("") @@ -858,7 +875,14 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value("") def apply_overrides(self, override_value): - self.set_value(override_value, override_value=True) + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.set_value(value) def _on_value_change(self, item=None): value = self.item_value() @@ -1157,7 +1181,14 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value([]) def apply_overrides(self, override_value): - self.set_value(override_value, override_value=True) + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + self.set_value(value) def update_style(self, is_overriden=None): if is_overriden is None: @@ -1291,6 +1322,14 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden + def apply_overrides(self, override_value): + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + def _on_value_change(self, item=None): if self.is_group: if self.is_overidable: @@ -1379,6 +1418,8 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): @@ -1418,6 +1459,9 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def update_style(self, *args, **kwargs): + return + @property def is_overriden(self): return self._parent.is_overriden @@ -1465,9 +1509,32 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): ) self.layout().addWidget(item) + item.value_changed.connect(self._on_value_change) + self.input_fields.append(item) return item + def _on_value_change(self, item=None): + if self.is_group: + if self.is_overidable: + self._is_overriden = True + # TODO update items + if item is not None: + is_overriden = self.is_overriden + for _item in self.input_fields: + if _item is not item: + _item.update_style(is_overriden) + + self.value_changed.emit(self) + + def apply_overrides(self, override_value): + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + class DictFormWidget(QtWidgets.QWidget): value_changed = QtCore.Signal(object) @@ -1777,6 +1844,10 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + value_changed = QtCore.Signal(object) + def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -1819,8 +1890,12 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def _on_value_change(self, item=None): self.child_modified = self.item_value() != self.default_value - if self.is_group and self.is_overidable: - self._is_overriden = True + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + self.value_changed.emit(self) self.update_style() @@ -1838,6 +1913,9 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden + def apply_overrides(self, override_value): + print(self, override_value) + def update_style(self, is_overriden=None): child_modified = self.child_modified if is_overriden is None: From 369e1cc08a2b8c8d637b1b17839a7e3fa7f91883 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 18:12:17 +0200 Subject: [PATCH 248/947] added testing data --- .../kuba_each_case/plugins/maya/publish.json | 8 ++++ .../config/studio_presets/global/tools.json | 6 +++ .../config_gui_schema/plugins_gui_schema.json | 37 +++++++++++++++---- 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/tools.json diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json new file mode 100644 index 0000000000..46fc343b6c --- /dev/null +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json @@ -0,0 +1,8 @@ +{ + "ValidateModelName": { + "enabled": true + }, + "ValidateAssemblyName": { + "enabled": false + } +} diff --git a/pype/tools/config_setting/config/studio_presets/global/tools.json b/pype/tools/config_setting/config/studio_presets/global/tools.json new file mode 100644 index 0000000000..53aab7b2ca --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/tools.json @@ -0,0 +1,6 @@ +{ + "mtoa_3.0.1": false, + "mtoa_3.1.1": false, + "mtoa_3.2.0": false, + "yeti_2.1.2": false +} \ No newline at end of file diff --git a/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json b/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json index 79c1f85b85..6c83fee172 100644 --- a/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json @@ -7,16 +7,37 @@ "key": "maya", "type": "dict-expanding", "label": "Maya", - "is_group": true, "children": [ { - "key": "test1", - "type": "raw-json", - "label": "Test1" - }, { - "key": "test2", - "type": "raw-json", - "label": "Test2" + "key": "publish", + "type": "dict-expanding", + "label": "Publish plugins", + "is_group": true, + "children": [ + { + "key": "ValidateModelName", + "type": "dict-invisible", + "label": "Validate Model Name", + "children": [ + { + "key": "enabled", + "type": "boolean", + "label": "Enabled" + } + ] + }, { + "key": "ValidateAssemblyName", + "type": "dict-invisible", + "label": "Validate Assembly Name", + "children": [ + { + "key": "enabled", + "type": "boolean", + "label": "Enabled" + } + ] + } + ] } ] } From d6a998d953cc68e0b9f8fce6588728bf7fc464d6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 6 Aug 2020 19:01:13 +0200 Subject: [PATCH 249/947] project change won't affect state attributes --- pype/tools/config_setting/widgets/inputs.py | 68 +++++++++++++++------ 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 53bb2d729d..a192a370b0 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -76,16 +76,29 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.default_value = self.item_value() self.override_value = None + self._ignore_value_change = False + self.checkbox.stateChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value( + self, value, *, + ignore_attr_changes=True, + default_value=False + ): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self._ignore_value_change = True + self.checkbox.setChecked(value) + if default_value: + ignore_attr_changes = True self.default_value = self.item_value() - self._on_value_change() + + self._on_value_change(ignore_attr_changes=ignore_attr_changes) def reset_value(self): - if self.is_overidable: + if self.is_overidable and self.override_value is not None: self.set_value(self.override_value) else: self.set_value(self.default_value) @@ -95,7 +108,6 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self.override_value = override_value - self._is_modified = False if override_value is None: self._is_overriden = False self._was_overriden = False @@ -104,7 +116,12 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._is_overriden = True self._was_overriden = True value = override_value - self.set_value(value) + + self._is_modified = False + + self.set_value(value, ignore_attr_changes=True) + + print("apply_overrides", self.keys, override_value) @property def child_modified(self): @@ -128,18 +145,23 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def _on_value_change(self, item=None): - _value = self.item_value() - is_modified = None - if self.is_overidable: - self._is_overriden = True - if self.override_value is not None: - is_modified = _value != self.override_value + def _on_value_change(self, item=None, ignore_attr_changes=False): + if self._ignore_value_change: + self._ignore_value_change = False + return - if is_modified is None: - is_modified = _value != self.default_value + if not ignore_attr_changes: + _value = self.item_value() + is_modified = None + if self.is_overidable: + self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value - self._is_modified = is_modified + if is_modified is None: + is_modified = _value != self.default_value + + self._is_modified = is_modified self.update_style() @@ -1293,6 +1315,8 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): keys.append(self.key) self.keys = keys + self._ignore_value_change = False + for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) @@ -1323,6 +1347,9 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._parent.is_overriden def apply_overrides(self, override_value): + self._ignore_value_change = True + + self._is_overriden = False for item in self.input_fields: if override_value is None: child_value = None @@ -1330,8 +1357,15 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): child_value = override_value.get(item.key) item.apply_overrides(child_value) - def _on_value_change(self, item=None): - if self.is_group: + self._ignore_value_change = False + + self._on_value_change(ignore_attr_changes=True) + + def _on_value_change(self, item=None, ignore_attr_changes=False): + if self._ignore_value_change: + return + + if not ignore_attr_changes and self.is_group: if self.is_overidable: self._is_overriden = True # TODO update items From 7ed3c2686aa20e8b95a4f2d598ca69ea60ce3d19 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Aug 2020 21:21:18 +0200 Subject: [PATCH 250/947] fix(sp): removing loggers for speedup --- .../publish/collect_hierarchy.py | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 8e48d141f2..7433dc9b3a 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -55,13 +55,10 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): continue self.hierarchy_data[type] = match[-1] - self.log.debug("__ hierarchy_data: {}".format(self.hierarchy_data)) - # format to new shot name self.shot_name = self.shot_rename_template.format( **self.hierarchy_data) instance.data["asset"] = self.shot_name - self.log.debug("__ self.shot_name: {}".format(self.shot_name)) def create_hierarchy(self, instance): parents = list() @@ -77,7 +74,6 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): visual_hierarchy.append( instance.context.data["projectEntity"]) break - self.log.debug("__ visual_hierarchy: {}".format(visual_hierarchy)) # add current selection context hierarchy from standalonepublisher for entity in reversed(visual_hierarchy): @@ -102,8 +98,6 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): instance.data["hierarchy"] = hierarchy instance.data["parents"] = parents - self.log.debug(f"_>_ hierarchy: {hierarchy}") - self.log.debug(f"_>_ parents: {parents}") if self.shot_add_tasks: instance.data["tasks"] = self.shot_add_tasks @@ -143,8 +137,6 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): # dealing with shared attributes across instances # with the same asset name if assets_shared.get(asset): - self.log.debug("Adding to shared assets: `{}`".format( - asset)) asset_shared = assets_shared.get(asset) else: asset_shared = assets_shared[asset] @@ -189,31 +181,14 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): # inject assetsShared to other instances with # the same `assetShareName` attribute in data asset_shared_name = instance.data.get("assetShareName") - self.log.debug(f"_ assets_shared: {assets_shared}") - self.log.debug(f"_ asset_shared_name: {asset_shared_name}") s_asset_data = assets_shared.get(asset_shared_name) if s_asset_data: - self.log.debug("__ s_asset_data: {}".format(s_asset_data)) instance.data["asset"] = s_asset_data["asset"] instance.data["parents"] = s_asset_data["parents"] instance.data["hierarchy"] = s_asset_data["hierarchy"] instance.data["tasks"] = s_asset_data["tasks"] - self.log.debug( - "__ instance.data[parents]: {}".format( - instance.data["parents"] - ) - ) - self.log.debug( - "__ instance.data[hierarchy]: {}".format( - instance.data["hierarchy"] - ) - ) - self.log.debug( - "__ instance.data[name]: {}".format(instance.data["name"]) - ) - # generate hierarchy data only on shot instances if 'shot' not in instance.data.get('family', ''): continue @@ -245,7 +220,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): in_info['tasks'] = instance.data['tasks'] parents = instance.data.get('parents', []) - self.log.debug("__ in_info: {}".format(in_info)) actual = {name: in_info} @@ -261,5 +235,4 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): # adding hierarchy context to instance context.data["hierarchyContext"] = final_context - self.log.debug("context.data[hierarchyContext] is: {}".format( - context.data["hierarchyContext"])) + self.log.info("Hierarchy instance collected") From 4261972a8def9374f5205d68e86d29730efe444a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Aug 2020 21:22:23 +0200 Subject: [PATCH 251/947] feat(sp): editorial with multiple edl file instances --- pype/modules/standalonepublish/publish.py | 10 +- .../publish/collect_clip_instances.py | 17 ++-- .../publish/collect_context.py | 95 +++++++++++++++---- .../publish/collect_editorial.py | 4 + 4 files changed, 91 insertions(+), 35 deletions(-) diff --git a/pype/modules/standalonepublish/publish.py b/pype/modules/standalonepublish/publish.py index d2d9eb486f..0a30d5f2cb 100644 --- a/pype/modules/standalonepublish/publish.py +++ b/pype/modules/standalonepublish/publish.py @@ -5,8 +5,7 @@ import tempfile import random import string -from avalon import io, api -import importlib +from avalon import io import pype from pype.api import execute, Logger @@ -62,8 +61,6 @@ def set_context(project, asset, task, app): def publish(data, gui=True): # cli pyblish seems like better solution return cli_publish(data, gui) - # # this uses avalon pyblish launch tool - # avalon_api_publish(data, gui) def cli_publish(data, gui=True): @@ -76,10 +73,6 @@ def cli_publish(data, gui=True): chars = "".join([random.choice(string.ascii_letters) for i in range(15)]) staging_dir = tempfile.mkdtemp(chars) - # create json for return data - return_data_path = ( - staging_dir + os.path.basename(staging_dir) + 'return.json' - ) # create also json and fill with data json_data_path = staging_dir + os.path.basename(staging_dir) + '.json' with open(json_data_path, 'w') as outfile: @@ -88,7 +81,6 @@ def cli_publish(data, gui=True): envcopy = os.environ.copy() envcopy["PYBLISH_HOSTS"] = "standalonepublisher" envcopy["SAPUBLISH_INPATH"] = json_data_path - envcopy["SAPUBLISH_OUTPATH"] = return_data_path envcopy["PYBLISHGUI"] = "pyblish_pype" envcopy["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index 028a8b6aad..f99e56095c 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -36,16 +36,18 @@ class CollectClipInstances(pyblish.api.InstancePlugin): custom_start_frame = None def process(self, instance): - staging_dir = os.path.normpath( tempfile.mkdtemp(prefix="pyblish_tmp_") ) # get context context = instance.context + # attribute for checking duplicity during creation + if not context.data.get("assetNameCheck"): + context.data["assetNameCheck"] = list() + # create asset_names conversion table if not context.data.get("assetsShared"): - self.log.debug("Created `assetsShared` in context") context.data["assetsShared"] = dict() # get timeline otio data @@ -55,13 +57,11 @@ class CollectClipInstances(pyblish.api.InstancePlugin): tracks = timeline.each_child( descended_from_type=otio.schema.track.Track ) - self.log.debug(f"__ tracks: `{tracks}`") # get data from avalon asset_entity = instance.context.data["assetEntity"] asset_data = asset_entity["data"] asset_name = asset_entity["name"] - self.log.debug(f"__ asset_entity: `{asset_entity}`") # Timeline data. handle_start = int(asset_data["handleStart"]) @@ -69,7 +69,6 @@ class CollectClipInstances(pyblish.api.InstancePlugin): instances = [] for track in tracks: - self.log.debug(f"__ track: `{track}`") try: track_start_frame = ( abs(track.source_range.start_time.value) @@ -77,8 +76,6 @@ class CollectClipInstances(pyblish.api.InstancePlugin): except AttributeError: track_start_frame = 0 - self.log.debug(f"__ track: `{track}`") - for clip in track.each_child(): # skip all generators like black ampty if isinstance( @@ -98,6 +95,11 @@ class CollectClipInstances(pyblish.api.InstancePlugin): clip_name = os.path.splitext(clip.name)[0].lower() name = f"{asset_name.split('_')[0]}_{clip_name}" + if name not in context.data["assetNameCheck"]: + context.data["assetNameCheck"].append(name) + else: + self.log.warning(f"duplicate shot name: {name}") + # frame ranges data clip_in = clip.range_in_parent().start_time.value clip_out = clip.range_in_parent().end_time_inclusive().value @@ -171,7 +173,6 @@ class CollectClipInstances(pyblish.api.InstancePlugin): }) instances.append(instance.context.create_instance( **subset_instance_data)) - self.log.debug(instance_data) context.data["assetsShared"][name] = { "_clipIn": clip_in, diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 8bd4e609ab..4dcb25f927 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -36,18 +36,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): def process(self, context): # get json paths from os and load them io.install() - input_json_path = os.environ.get("SAPUBLISH_INPATH") - output_json_path = os.environ.get("SAPUBLISH_OUTPATH") - - # context.data["stagingDir"] = os.path.dirname(input_json_path) - context.data["returnJsonPath"] = output_json_path - - with open(input_json_path, "r") as f: - in_data = json.load(f) - - asset_name = in_data["asset"] - family = in_data["family"] - subset = in_data["subset"] # Load presets presets = context.data.get("presets") @@ -57,19 +45,92 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): presets = config.get_presets() project = io.find_one({"type": "project"}) - asset = io.find_one({"type": "asset", "name": asset_name}) context.data["project"] = project + + # get json file context + input_json_path = os.environ.get("SAPUBLISH_INPATH") + + with open(input_json_path, "r") as f: + in_data = json.load(f) + self.log.debug(f"_ in_data: {in_data}") + + self.asset_name = in_data["asset"] + self.family = in_data["family"] + asset = io.find_one({"type": "asset", "name": self.asset_name}) context.data["asset"] = asset + # exception for editorial + if "editorial" in self.family: + # avoid subset name duplicity + if not context.data.get("subsetNamesCheck"): + context.data["subsetNamesCheck"] = list() + + in_data_list = list() + representations = in_data.pop("representations") + for repr in representations: + in_data_copy = in_data.copy() + ext = repr["ext"][1:] + subset = in_data_copy["subset"] + # filter out non editorial files + if ext not in ["edl", "xml"]: + in_data_copy["representations"] = [repr] + in_data_copy["subset"] = f"{ext}{subset}" + in_data_list.append(in_data_copy) + + files = repr.pop("files") + + # delete unneeded keys + delete_repr_keys = ["frameStart", "frameEnd"] + for k in delete_repr_keys: + if repr.get(k): + repr.pop(k) + + # convert files to list if it isnt + if not isinstance(files, list): + files = [files] + + self.log.debug(f"_ files: {files}") + for index, f in enumerate(files): + index += 1 + # copy dictionaries + in_data_copy = in_data_copy.copy() + repr_new = repr.copy() + + repr_new["files"] = f + repr_new["name"] = ext + in_data_copy["representations"] = [repr_new] + + # create subset Name + new_subset = f"{ext}{index}{subset}" + while new_subset in context.data["subsetNamesCheck"]: + index += 1 + new_subset = f"{ext}{index}{subset}" + + context.data["subsetNamesCheck"].append(new_subset) + in_data_copy["subset"] = new_subset + in_data_list.append(in_data_copy) + self.log.info(f"Creating subset: {ext}{index}{subset}") + else: + in_data_list = [in_data] + + self.log.debug(f"_ in_data_list: {in_data_list}") + + for in_data in in_data_list: + # create instance + self.create_instance(context, in_data) + + def create_instance(self, context, in_data): + subset = in_data["subset"] + instance = context.create_instance(subset) instance.data.update( { "subset": subset, - "asset": asset_name, + "asset": self.asset_name, "label": subset, "name": subset, - "family": family, + "family": self.family, "version": in_data.get("version", 1), "frameStart": in_data.get("representations", [None])[0].get( "frameStart", None @@ -77,7 +138,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "frameEnd": in_data.get("representations", [None])[0].get( "frameEnd", None ), - "families": [family, "ftrack"], + "families": [self.family, "ftrack"], } ) self.log.info("collected instance: {}".format(instance.data)) @@ -105,5 +166,3 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): self.log.debug("Adding review family") instance.data["representations"].append(component) - - self.log.info(in_data) diff --git a/pype/plugins/standalonepublisher/publish/collect_editorial.py b/pype/plugins/standalonepublisher/publish/collect_editorial.py index 406848628a..a31125d9a8 100644 --- a/pype/plugins/standalonepublisher/publish/collect_editorial.py +++ b/pype/plugins/standalonepublisher/publish/collect_editorial.py @@ -35,6 +35,10 @@ class CollectEditorial(pyblish.api.InstancePlugin): extensions = [".mov"] def process(self, instance): + # remove context test attribute + if instance.context.data.get("subsetNamesCheck"): + instance.context.data.pop("subsetNamesCheck") + self.log.debug(f"__ instance: `{instance}`") # get representation with editorial file for representation in instance.data["representations"]: From 4bd0ab0b7b0cfb19f2441e362abb0a8af71f01ea Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Aug 2020 21:22:47 +0200 Subject: [PATCH 252/947] fix(sp): improving data collection --- pype/modules/standalonepublish/widgets/widget_drop_frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/modules/standalonepublish/widgets/widget_drop_frame.py index c91e906f45..57547a3d5f 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_frame.py @@ -357,7 +357,7 @@ class DropDataFrame(QtWidgets.QFrame): if data['name'] == item.in_data['name']: found = True break - paths = data['files'] + paths = list(data['files']) paths.extend(item.in_data['files']) c, r = clique.assemble(paths) if len(c) == 0: @@ -392,7 +392,7 @@ class DropDataFrame(QtWidgets.QFrame): else: if data['name'] != item.in_data['name']: continue - if data['files'] == item.in_data['files']: + if data['files'] == list(item.in_data['files']): found = True break a_name = 'merge' From d72f5a798e14da0fec26a371c38a20b7f469169c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Aug 2020 21:23:32 +0200 Subject: [PATCH 253/947] feat(sp): multi-threading hierarchy collection --- .../publish/collect_hierarchy.py | 77 +++++++++++++------ 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 7433dc9b3a..f518813bfe 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -2,9 +2,11 @@ import pyblish.api import re import os from avalon import io +import queue +import threading -class CollectHierarchyInstance(pyblish.api.InstancePlugin): +class CollectHierarchyInstance(pyblish.api.ContextPlugin): """Collecting hierarchy context from `parents` and `hierarchy` data present in `clip` family instances coming from the request json data file @@ -18,6 +20,11 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): hosts = ["standalonepublisher"] families = ["shot"] + # multiprocessing + num_worker_threads = 10 + queue = queue.Queue() + threads = [] + # presets shot_rename_template = None shot_rename_search_patterns = None @@ -41,29 +48,27 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): def rename_with_hierarchy(self, instance): search_text = "" - parent_name = self.asset_entity["name"] + parent_name = instance.context.data["assetEntity"]["name"] clip = instance.data["item"] clip_name = os.path.splitext(clip.name)[0].lower() - if self.shot_rename_search_patterns: search_text += parent_name + clip_name - self.hierarchy_data.update({"clip_name": clip_name}) + instance.data["anatomyData"].update({"clip_name": clip_name}) for type, pattern in self.shot_rename_search_patterns.items(): p = re.compile(pattern) match = p.findall(search_text) if not match: continue - self.hierarchy_data[type] = match[-1] + instance.data["anatomyData"][type] = match[-1] # format to new shot name - self.shot_name = self.shot_rename_template.format( - **self.hierarchy_data) - instance.data["asset"] = self.shot_name + instance.data["asset"] = self.shot_rename_template.format( + **instance.data["anatomyData"]) def create_hierarchy(self, instance): parents = list() hierarchy = "" - visual_hierarchy = [self.asset_entity] + visual_hierarchy = [instance.context.data["assetEntity"]] while True: visual_parent = io.find_one( {"_id": visual_hierarchy[-1]["data"]["visualParent"]} @@ -88,7 +93,7 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): hierarchy_parents = shot_add_hierarchy["parents"].copy() for parent in hierarchy_parents: hierarchy_parents[parent] = hierarchy_parents[parent].format( - **self.hierarchy_data) + **instance.data["anatomyData"]) prnt = self.convert_to_entity( parent, hierarchy_parents[parent]) parents.append(prnt) @@ -105,20 +110,49 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): instance.data["tasks"] = list() # updating hierarchy data - self.hierarchy_data.update({ - "asset": self.shot_name, + instance.data["anatomyData"].update({ + "asset": instance.data["asset"], "task": "conform" }) - def process(self, instance): + def queue_worker(self, worker_index): + while True: + instance = self.queue.get() + if instance is None: + break + self.processing_instance(instance, worker_index) + self.queue.task_done() + + def process(self, context): + for i in range(self.num_worker_threads): + t = threading.Thread(target=self.queue_worker, args=(i,)) + t.start() + self.threads.append(t) + + for instance in context: + if instance.data["family"] in self.families: + self.queue.put(instance) + + # block until all tasks are done + self.queue.join() + + self.log.info('stopping workers!') + + # stop workers + for i in range(self.num_worker_threads): + self.queue.put(None) + + for t in self.threads: + t.join() + + def processing_instance(self, instance, worker_index): + self.log.info(f"_ worker_index: {worker_index}") + self.log.info(f"_ instance: {instance}") + # adding anatomyData for burnins + instance.data["anatomyData"] = instance.context.data["anatomyData"] + asset = instance.data["asset"] assets_shared = instance.context.data.get("assetsShared") - context = instance.context - anatomy_data = context.data["anatomyData"] - - self.shot_name = instance.data["asset"] - self.hierarchy_data = dict(anatomy_data) - self.asset_entity = context.data["assetEntity"] frame_start = instance.data["frameStart"] frame_end = instance.data["frameEnd"] @@ -128,10 +162,9 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): self.create_hierarchy(instance) - # adding anatomyData for burnins - instance.data["anatomyData"] = self.hierarchy_data + shot_name = instance.data["asset"] - label = f"{self.shot_name} ({frame_start}-{frame_end})" + label = f"{shot_name} ({frame_start}-{frame_end})" instance.data["label"] = label # dealing with shared attributes across instances From ca948504997739e334e661b680ad5bbc72d04d01 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 11:59:11 +0200 Subject: [PATCH 254/947] added testing schema --- .../config_gui_schema/project_gui_schema.json | 1 + .../test_project_schema.json | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 pype/tools/config_setting/config_gui_schema/test_project_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json index d2a6221f99..366400e5ff 100644 --- a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json @@ -6,6 +6,7 @@ { "type": "schema", "children": [ + "test_project_schema", "ftrack_projects_gui_schema", "plugins_gui_schema" ] diff --git a/pype/tools/config_setting/config_gui_schema/test_project_schema.json b/pype/tools/config_setting/config_gui_schema/test_project_schema.json new file mode 100644 index 0000000000..e789b422a3 --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/test_project_schema.json @@ -0,0 +1,33 @@ +{ + "key": "test_inputs", + "type": "dict-expanding", + "label": "Test inputs", + "is_group": true, + "children": [ + { + "key": "boolean", + "type": "boolean", + "label": "Boolean" + }, { + "key": "test_singleline", + "type": "text-singleline", + "label": "Text singleline" + }, { + "key": "test_multiline", + "type": "text-multiline", + "label": "Text multiline" + }, { + "key": "raw_json", + "type": "raw-json", + "label": "Raw json" + }, { + "key": "int", + "type": "int", + "label": "Int" + }, { + "key": "float", + "type": "float", + "label": "Float" + } + ] +} From 3d24d4db53fbff285ffa67658f27486bf7d37497 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 11:59:29 +0200 Subject: [PATCH 255/947] added ignore to integer --- pype/tools/config_setting/widgets/inputs.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index a192a370b0..7e91d11a21 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -121,8 +121,6 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(value, ignore_attr_changes=True) - print("apply_overrides", self.keys, override_value) - @property def child_modified(self): return self.is_modified @@ -248,6 +246,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.default_value = self.item_value() self.override_value = None + self._ignore_value_change = False + self.int_input.valueChanged.connect(self._on_value_change) @property @@ -294,10 +294,15 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): value = override_value self.set_value(value) - def _on_value_change(self, item=None): - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True + def _on_value_change(self, item=None, ignore_attr_changes=False): + if self._ignore_value_change: + self._ignore_value_change = False + return + + if not ignore_attr_changes: + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True self.update_style() @@ -634,7 +639,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_group = True self.is_group = is_group - self.is_modified = False + self._is_modified = False self._was_overriden = False self._is_overriden = False From 5724f3e474cf59254d2d15c049adc761d300606b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 12:18:54 +0200 Subject: [PATCH 256/947] using -g 1 (same as -intra) to set right keyframes of burning output --- pype/scripts/otio_burnin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 104ff0255c..16e24757dd 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -528,6 +528,9 @@ def burnins_from_data( if pix_fmt: ffmpeg_args.append("-pix_fmt {}".format(pix_fmt)) + # Use group one (same as `-intra` argument, which is deprecated) + ffmpeg_args.append("-g 1") + ffmpeg_args_str = " ".join(ffmpeg_args) burnin.render( output_path, args=ffmpeg_args_str, overwrite=overwrite, **data From 941f6be192f46e7ba8c9042e61b73bf7875c0663 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 12:39:47 +0200 Subject: [PATCH 257/947] query also avalon group in get_pype_attr --- pype/modules/ftrack/lib/avalon_sync.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index f78e617f3a..3548968628 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -81,10 +81,11 @@ def check_regex(name, entity_type, in_schema=None, schema_patterns=None): def get_pype_attr(session, split_hierarchical=True): custom_attributes = [] hier_custom_attributes = [] + # TODO remove deprecated "avalon" group from query cust_attrs_query = ( "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" - " where group.name = \"{}\"" + " where group.name in (\"{}\", \"avalon\")" ).format(CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: From ce3271de02a082ce06aecbeea3a15acbf10f282d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 12:41:53 +0200 Subject: [PATCH 258/947] query also pype group in ftrack attributes --- pype/modules/ftrack/lib/avalon_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index c5c9eb9054..885b9d25cc 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -70,7 +70,7 @@ def get_avalon_attr(session, split_hierarchical=True): cust_attrs_query = ( "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" - " where group.name = \"avalon\"" + " where group.name in (\"avalon\", \"pype\")" ) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: From 23caec0a76d10b660f1a11d462e825d6f2163feb Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 7 Aug 2020 13:45:32 +0200 Subject: [PATCH 259/947] fix case where staging dir is not set --- pype/plugins/global/publish/cleanup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/cleanup.py b/pype/plugins/global/publish/cleanup.py index bca540078f..e891b7b7f6 100644 --- a/pype/plugins/global/publish/cleanup.py +++ b/pype/plugins/global/publish/cleanup.py @@ -71,12 +71,16 @@ class CleanUp(pyblish.api.InstancePlugin): temp_root = tempfile.gettempdir() staging_dir = instance.data.get("stagingDir", None) + if not staging_dir: + self.log.info("Staging dir not set.") + return + if not os.path.normpath(staging_dir).startswith(temp_root): self.log.info("Skipping cleanup. Staging directory is not in the " "temp folder: %s" % staging_dir) return - if not staging_dir or not os.path.exists(staging_dir): + if not os.path.exists(staging_dir): self.log.info("No staging directory found: %s" % staging_dir) return From d57f0faf5340979862a1bab795e6367ad0a73ae2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Aug 2020 16:31:52 +0200 Subject: [PATCH 260/947] fix(sp): one frame offset --- .../publish/collect_clip_instances.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index f99e56095c..180e213342 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -32,7 +32,7 @@ class CollectClipInstances(pyblish.api.InstancePlugin): "families": [] } } - start_frame_offset = None # if 900000 for edl default then -900000 + timeline_frame_offset = None # if 900000 for edl default then -900000 custom_start_frame = None def process(self, instance): @@ -103,7 +103,14 @@ class CollectClipInstances(pyblish.api.InstancePlugin): # frame ranges data clip_in = clip.range_in_parent().start_time.value clip_out = clip.range_in_parent().end_time_inclusive().value - clip_duration = clip.duration().value + + # add offset in case there is any + if self.timeline_frame_offset: + clip_in += self.timeline_frame_offset + clip_out += self.timeline_frame_offset + + clip_duration = clip.duration().value - 1 + self.log.info(f"clip duration: {clip_duration}") source_in = clip.trimmed_range().start_time.value source_out = source_in + clip_duration @@ -114,13 +121,13 @@ class CollectClipInstances(pyblish.api.InstancePlugin): clip_out_h = clip_out + handle_end # define starting frame for future shot - frame_start = self.custom_start_frame or clip_in - - # add offset in case there is any - if self.start_frame_offset: - frame_start += self.start_frame_offset + if self.custom_start_frame is not None: + frame_start = self.custom_start_frame + else: + frame_start = clip_in frame_end = frame_start + clip_duration + self.log.info(f"frames: {frame_start}-{frame_end}") # create shared new instance data instance_data = { From b4070f5f4ecc1a70e075b70f049c8b5323ecea0c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Aug 2020 16:32:37 +0200 Subject: [PATCH 261/947] clean(sp): prints --- .../standalonepublisher/publish/collect_clip_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index 180e213342..a8603a50b4 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -127,7 +127,6 @@ class CollectClipInstances(pyblish.api.InstancePlugin): frame_start = clip_in frame_end = frame_start + clip_duration - self.log.info(f"frames: {frame_start}-{frame_end}") # create shared new instance data instance_data = { From 4b291a286a2317b926577bbdfb22010e66f14044 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 7 Aug 2020 17:17:36 +0200 Subject: [PATCH 262/947] fix collection assembly and extension determination --- pype/plugins/global/publish/submit_publish_job.py | 2 +- pype/plugins/maya/publish/submit_maya_deadline.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index b6d62a8fd1..dd9e84aade 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -441,7 +441,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # but we really expect only one collection. # Nothing else make sense. assert len(cols) == 1, "only one image sequence type is expected" # noqa: E501 - _, ext = os.path.splitext(cols[0].tail) + ext = cols[0].tail.lstrip(".") col = list(cols[0]) self.log.debug(col) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 74185164b1..7fe20c779d 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -393,7 +393,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 output_file = rem[0] else: - output_file = col.format('{head}{padding}{tail}') + output_file = col[0].format('{head}{padding}{tail}') payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 output_filenames[exp_index] = output_file exp_index += 1 @@ -407,7 +407,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "with them.") payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 else: - output_file = col.format('{head}{padding}{tail}') + output_file = col[0].format('{head}{padding}{tail}') payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 plugin = payload["JobInfo"]["Plugin"] From 1c5b923888cbb2ca2246e2f7150fd0e816be7186 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 17:18:36 +0200 Subject: [PATCH 263/947] overrides are apllies in right way --- pype/tools/config_setting/widgets/base.py | 5 + pype/tools/config_setting/widgets/inputs.py | 220 ++++++++++++++------ 2 files changed, 156 insertions(+), 69 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index a90eefd400..9413b07733 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -63,6 +63,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_overriden = False is_group = False any_parent_is_group = False + ignore_value_changes = False def __init__(self, parent=None): super(StudioWidget, self).__init__(parent) @@ -281,6 +282,8 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): super(ProjectWidget, self).__init__(parent) self.is_overidable = False + self.ignore_value_changes = False + self.input_fields = [] scroll_widget = QtWidgets.QScrollArea(self) @@ -354,8 +357,10 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): overrides = config.project_preset_overrides(project_name) self.is_overidable = True + self.ignore_value_changes = True for item in self.input_fields: item.apply_overrides(overrides) + self.ignore_value_changes = False def _save(self): output = {} diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 7e91d11a21..a0226d7b73 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -76,26 +76,17 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.default_value = self.item_value() self.override_value = None - self._ignore_value_change = False - self.checkbox.stateChanged.connect(self._on_value_change) - def set_value( - self, value, *, - ignore_attr_changes=True, - default_value=False - ): + def set_value(self, value, *, default_value=False): # Ignore value change because if `self.isChecked()` has same # value as `value` the `_on_value_change` is not triggered - self._ignore_value_change = True - self.checkbox.setChecked(value) if default_value: - ignore_attr_changes = True self.default_value = self.item_value() - self._on_value_change(ignore_attr_changes=ignore_attr_changes) + self._on_value_change() def reset_value(self): if self.is_overidable and self.override_value is not None: @@ -119,7 +110,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._is_modified = False - self.set_value(value, ignore_attr_changes=True) + self.set_value(value) + self.update_style() @property def child_modified(self): @@ -143,23 +135,25 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden - def _on_value_change(self, item=None, ignore_attr_changes=False): - if self._ignore_value_change: - self._ignore_value_change = False + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: return - if not ignore_attr_changes: - _value = self.item_value() - is_modified = None - if self.is_overidable: - self._is_overriden = True - if self.override_value is not None: - is_modified = _value != self.override_value + _value = self.item_value() + is_modified = None + if self.is_overidable: + self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value - if is_modified is None: - is_modified = _value != self.default_value + if is_modified is None: + is_modified = _value != self.default_value - self._is_modified = is_modified + self._is_modified = is_modified self.update_style() @@ -246,8 +240,6 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.default_value = self.item_value() self.override_value = None - self._ignore_value_change = False - self.int_input.valueChanged.connect(self._on_value_change) @property @@ -272,6 +264,10 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): return self._is_overriden return self._parent.is_overriden + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + def set_value(self, value, *, default_value=False): self.int_input.setValue(value) if default_value: @@ -288,21 +284,25 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.override_value = override_value if override_value is None: self._is_overriden = False + self._was_overriden = False value = self.default_value else: self._is_overriden = True + self._was_overriden = True value = override_value - self.set_value(value) - def _on_value_change(self, item=None, ignore_attr_changes=False): - if self._ignore_value_change: - self._ignore_value_change = False + self._is_modified = False + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: return - if not ignore_attr_changes: - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True self.update_style() @@ -420,9 +420,11 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def set_value(self, value, *, default_value=False): self.float_input.setValue(value) @@ -441,12 +443,17 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: self._is_overriden = True value = override_value + self.set_value(value) + self.update_style() def clear_value(self): self.set_value(0) def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -557,9 +564,11 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def set_value(self, value, *, default_value=False): self.text_input.setText(value) @@ -571,19 +580,27 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.default_value) def apply_overrides(self, override_value): + self._is_modified = False self.override_value = override_value if override_value is None: self._is_overriden = False + self._was_overriden = False value = self.default_value else: self._is_overriden = True + self._was_overriden = True value = override_value + self.set_value(value) + self.update_style() def clear_value(self): self.set_value("") def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -692,9 +709,11 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) @@ -713,12 +732,17 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: self._is_overriden = True value = override_value + self.set_value(value) + self.update_style() def clear_value(self): self.set_value("") def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -885,9 +909,11 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) @@ -909,11 +935,15 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: self._is_overriden = True value = override_value + self.set_value(value) + self.update_style() def _on_value_change(self, item=None): - value = self.item_value() - self._is_modified = value != self.default_value + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -1182,11 +1212,15 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def _on_value_change(self, item=None): + if self.ignore_value_changes: + return self._is_modified = self.item_value() != self.default_value if self.is_overidable: self._is_overriden = True @@ -1215,7 +1249,9 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): else: self._is_overriden = True value = override_value + self.set_value(value) + self.update_style() def update_style(self, is_overriden=None): if is_overriden is None: @@ -1320,8 +1356,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): keys.append(self.key) self.keys = keys - self._ignore_value_change = False - for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) @@ -1347,30 +1381,39 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def apply_overrides(self, override_value): - self._ignore_value_change = True - + # Make sure this is set to False self._is_overriden = False + for item in self.input_fields: if override_value is None: child_value = None else: child_value = override_value.get(item.key) + item.apply_overrides(child_value) - self._ignore_value_change = False + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() - self._on_value_change(ignore_attr_changes=True) - - def _on_value_change(self, item=None, ignore_attr_changes=False): - if self._ignore_value_change: + def _on_value_change(self, item=None): + if self.ignore_value_changes: return - if not ignore_attr_changes and self.is_group: + if self.is_group: if self.is_overidable: self._is_overriden = True # TODO update items @@ -1457,6 +1500,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + # TODO is not overridable by itself value_changed = QtCore.Signal(object) def __init__( @@ -1474,6 +1518,7 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): self.any_parent_is_group = any_parent_is_group + self._is_overriden = False self.is_modified = False self.is_group = is_group @@ -1503,7 +1548,7 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden @property def is_overidable(self): @@ -1523,6 +1568,10 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): return True return False + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + def item_value(self): output = {} for input_field in self.input_fields: @@ -1554,6 +1603,9 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): return item def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + if self.is_group: if self.is_overidable: self._is_overriden = True @@ -1567,6 +1619,7 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) def apply_overrides(self, override_value): + self._is_overriden = False for item in self.input_fields: if override_value is None: child_value = None @@ -1574,6 +1627,16 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): child_value = override_value.get(item.key) item.apply_overrides(child_value) + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + class DictFormWidget(QtWidgets.QWidget): value_changed = QtCore.Signal(object) @@ -1604,6 +1667,8 @@ class DictFormWidget(QtWidgets.QWidget): self.add_children_gui(child_data, values) def _on_value_change(self, item=None): + if self.ignore_value_changes: + return self.value_changed.emit(self) def item_value(self): @@ -1632,6 +1697,10 @@ class DictFormWidget(QtWidgets.QWidget): def is_overidable(self): return self._parent.is_overidable + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + def config_value(self): return self.item_value() @@ -1729,6 +1798,10 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def is_overriden(self): return self._parent.is_overriden + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + def is_key_modified(self): return self._key() != self.default_key @@ -1814,6 +1887,10 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_group(self): return self._parent.is_group + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + @property def any_parent_is_group(self): return self._parent.any_parent_is_group @@ -1928,6 +2005,9 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.override_value = None def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self.child_modified = self.item_value() != self.default_value if self.is_group: @@ -1948,9 +2028,11 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes def apply_overrides(self, override_value): print(self, override_value) From b6f948826dd1c471e7299d959c456b04e9771646 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 17:31:11 +0200 Subject: [PATCH 264/947] fix is modified appliance --- pype/tools/config_setting/widgets/inputs.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index a0226d7b73..8464eb8459 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -98,6 +98,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.reset_value() def apply_overrides(self, override_value): + self._is_modified = False self.override_value = override_value if override_value is None: self._is_overriden = False @@ -108,8 +109,6 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._was_overriden = True value = override_value - self._is_modified = False - self.set_value(value) self.update_style() @@ -260,9 +259,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden @property def ignore_value_changes(self): @@ -281,6 +278,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.default_value) def apply_overrides(self, override_value): + self._is_modified = False + self.override_value = override_value if override_value is None: self._is_overriden = False @@ -291,8 +290,6 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._was_overriden = True value = override_value - self._is_modified = False - self.set_value(value) self.update_style() @@ -436,6 +433,8 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.default_value) def apply_overrides(self, override_value): + self._is_modified = False + self.override_value = override_value if override_value is None: self._is_overriden = False @@ -581,6 +580,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False + self.override_value = override_value if override_value is None: self._is_overriden = False @@ -725,6 +725,8 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(self.default_value) def apply_overrides(self, override_value): + self._is_modified = False + self.override_value = override_value if override_value is None: self._is_overriden = False @@ -928,6 +930,8 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value("") def apply_overrides(self, override_value): + self._is_modified = False + self.override_value = override_value if override_value is None: self._is_overriden = False @@ -1242,6 +1246,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value([]) def apply_overrides(self, override_value): + self._is_modified = False + self.override_value = override_value if override_value is None: self._is_overriden = False From 059a0c6ae039f9a0f9aa1966346760364e1734b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Aug 2020 18:09:57 +0200 Subject: [PATCH 265/947] feat(ps): audio main to version 1 only and fixing 0.5 frame offset --- .../publish/collect_clip_instances.py | 7 ++--- .../publish/extract_shot_data.py | 26 +++++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index a8603a50b4..f4d2810f2b 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -25,7 +25,8 @@ class CollectClipInstances(pyblish.api.InstancePlugin): "family": "audio", "families": ["ftrack"], "ftrackFamily": "audio", - "extension": ".wav" + "extension": ".wav", + "version": 1 }, "shotMain": { "family": "shot", @@ -109,7 +110,7 @@ class CollectClipInstances(pyblish.api.InstancePlugin): clip_in += self.timeline_frame_offset clip_out += self.timeline_frame_offset - clip_duration = clip.duration().value - 1 + clip_duration = clip.duration().value self.log.info(f"clip duration: {clip_duration}") source_in = clip.trimmed_range().start_time.value @@ -126,7 +127,7 @@ class CollectClipInstances(pyblish.api.InstancePlugin): else: frame_start = clip_in - frame_end = frame_start + clip_duration + frame_end = frame_start + (clip_duration - 1) # create shared new instance data instance_data = { diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py index 2732d0fa59..5b9fb43ffa 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot_data.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -42,15 +42,31 @@ class ExtractShotData(pype.api.Extractor): # input_data = plib.ffprobe_streams(video_file_path)[0] # self.log.debug(f"__ input_data: `{input_data}`") + start = float(instance.data["clipInH"]) + dur = float(instance.data["clipDurationH"]) + + if ext in ".wav": + start += 0.5 + dur += 0.5 + args = [ ffmpeg_path, - "-ss", str(instance.data["clipInH"] / fps), + "-ss", str(start / fps), "-i", video_file_path, - "-t", str(instance.data["clipDurationH"] / fps), - "-crf", "18", - "-pix_fmt", "yuv420p", - clip_trimed_path + "-t", str(dur / fps) ] + if ext in [".mov", ".mp4"]: + args.extend([ + "-crf", "18", + "-pix_fmt", "yuv420p"]) + elif ext in ".wav": + args.extend([ + "-vn -acodec pcm_s16le", + "-ar 44100 -ac 2"]) + + # add output path + args.append(clip_trimed_path) + self.log.info(f"Processing: {args}") ffmpeg_args = " ".join(args) output = pype.api.subprocess(ffmpeg_args) From 02051472d6813abc335cae002088e8b615ebd36b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 18:12:22 +0200 Subject: [PATCH 266/947] attribute cleanup on apply overrides --- pype/tools/config_setting/widgets/inputs.py | 100 +++++++------------- 1 file changed, 34 insertions(+), 66 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 8464eb8459..e0a18afe1d 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -99,6 +99,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -122,7 +123,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self._is_overriden) + return self._is_modified or (self._was_overriden != self.is_overriden) @property def is_overidable(self): @@ -130,9 +131,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): @property def is_overriden(self): - if self._is_overriden: - return self._is_overriden - return self._parent.is_overriden + return self._is_overriden or self._parent.is_overriden @property def ignore_value_changes(self): @@ -158,12 +157,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -279,7 +274,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False - + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -305,12 +300,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -434,7 +425,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False - + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -461,12 +452,8 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -580,7 +567,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False - + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -609,12 +596,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -726,7 +709,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False - + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -753,12 +736,8 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -931,7 +910,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False - + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -955,12 +934,8 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.value_changed.emit(self) - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -1247,7 +1222,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): self._is_modified = False - + self._state = None self.override_value = override_value if override_value is None: self._is_overriden = False @@ -1259,12 +1234,8 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.set_value(value) self.update_style() - def update_style(self, is_overriden=None): - if is_overriden is None: - is_overriden = self.is_overriden - is_modified = self.is_modified - - state = self.style_state(is_overriden, is_modified) + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -1396,7 +1367,8 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False - + self._state = None + self._child_state = None for item in self.input_fields: if override_value is None: child_value = None @@ -1427,7 +1399,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): is_overriden = self.is_overriden for _item in self.input_fields: if _item is not item: - _item.update_style(is_overriden) + _item.update_style() self.value_changed.emit(self) @@ -1435,20 +1407,16 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def update_style(self, is_overriden=None): child_modified = self.child_modified - if is_overriden is None: - child_overriden = self.child_overriden - child_state = self.style_state(child_overriden, child_modified) - if child_state: - child_state = "child-{}".format(child_state) + child_state = self.style_state(self.child_overriden, child_modified) + if child_state: + child_state = "child-{}".format(child_state) - if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) - self._child_state = child_state - - if is_overriden is None: - is_overriden = self.is_overriden + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + is_overriden = self.is_overriden if child_modified and not is_overriden: state = self.default_state else: From 54d0c8f4b0dc52d20086e8cc9062404df688b060 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 7 Aug 2020 18:13:43 +0200 Subject: [PATCH 267/947] fix(sp): paths with space were not supported --- .../publish/collect_clip_instances.py | 6 +++--- .../standalonepublisher/publish/extract_shot_data.py | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index f4d2810f2b..3d9773d0b2 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -78,6 +78,9 @@ class CollectClipInstances(pyblish.api.InstancePlugin): track_start_frame = 0 for clip in track.each_child(): + if clip.name is None: + continue + # skip all generators like black ampty if isinstance( clip.media_reference, @@ -89,9 +92,6 @@ class CollectClipInstances(pyblish.api.InstancePlugin): if isinstance(clip, otio.schema.transition.Transition): continue - if clip.name is None: - continue - # basic unique asset name clip_name = os.path.splitext(clip.name)[0].lower() name = f"{asset_name.split('_')[0]}_{clip_name}" diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py index 5b9fb43ffa..e0b6196279 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot_data.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -52,7 +52,7 @@ class ExtractShotData(pype.api.Extractor): args = [ ffmpeg_path, "-ss", str(start / fps), - "-i", video_file_path, + "-i", f"\"{video_file_path}\"", "-t", str(dur / fps) ] if ext in [".mov", ".mp4"]: @@ -65,7 +65,7 @@ class ExtractShotData(pype.api.Extractor): "-ar 44100 -ac 2"]) # add output path - args.append(clip_trimed_path) + args.append(f"\"{clip_trimed_path}\"") self.log.info(f"Processing: {args}") ffmpeg_args = " ".join(args) @@ -96,7 +96,11 @@ class ExtractShotData(pype.api.Extractor): clip_img_sequence = os.path.join( staging_dir, instance.data["name"] + ".%04d.jpeg" ) - args = [ffmpeg_path, "-i", clip_trimed_path, clip_img_sequence] + args = [ + ffmpeg_path, "-i", + f"\"{clip_trimed_path}\"", + f"\"{clip_img_sequence}\"" + ] self.log.info(f"Processing: {args}") output = pype.lib._subprocess(args) self.log.info(output) From 1a6a5e4b7fd89dc53513230781d96676dc31c16d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 12:18:54 +0200 Subject: [PATCH 268/947] using -g 1 (same as -intra) to set right keyframes of burning output --- pype/scripts/otio_burnin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 104ff0255c..16e24757dd 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -528,6 +528,9 @@ def burnins_from_data( if pix_fmt: ffmpeg_args.append("-pix_fmt {}".format(pix_fmt)) + # Use group one (same as `-intra` argument, which is deprecated) + ffmpeg_args.append("-g 1") + ffmpeg_args_str = " ".join(ffmpeg_args) burnin.render( output_path, args=ffmpeg_args_str, overwrite=overwrite, **data From aee77e630c36f5f1ccd064948b8acda0c9c109f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 18:50:42 +0200 Subject: [PATCH 269/947] modifiabledict is working better now --- pype/tools/config_setting/widgets/inputs.py | 74 ++++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index e0a18afe1d..839fe1e8a2 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1786,7 +1786,7 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def is_modified(self): return self.is_value_modified() or self.is_key_modified() - def update_style(self, is_overriden=None): + def update_style(self): if self.is_key_modified(): state = "modified" else: @@ -1957,10 +1957,11 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): self.any_parent_is_group = any_parent_is_group - self.is_modified = False - self.child_modified = False - self._is_overriden = False self.is_group = is_group + self._is_modified = False + self._is_overriden = False + self._was_overriden = False + self._state = None super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") @@ -1982,16 +1983,26 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): if self.ignore_value_changes: return - self.child_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True - if self.is_group: - if self.is_overidable: - self._is_overriden = True + if self.is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.default_value self.value_changed.emit(self) self.update_style() + @property + def child_modified(self): + return self.is_modified + + @property + def is_modified(self): + return self._is_modified + @property def child_overriden(self): return self._is_overriden @@ -2004,29 +2015,42 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def is_overriden(self): return self._is_overriden or self._parent.is_overriden + @property + def is_modified(self): + return self._is_modified + @property def ignore_value_changes(self): return self._parent.ignore_value_changes def apply_overrides(self, override_value): - print(self, override_value) - - def update_style(self, is_overriden=None): - child_modified = self.child_modified - if is_overriden is None: - is_overriden = self.is_overriden - - child_overriden = self.child_overriden - child_state = self.style_state(child_overriden, child_modified) - if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) - self._child_state = child_state - - if child_modified and not is_overriden: - state = self.default_state + self._state = None + self._is_modified = False + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value else: - state = self.style_state(self.is_overriden, child_modified) + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.setProperty("state", child_state) + self.style().polish(self) self.label_widget.setProperty("state", state) self.label_widget.style().polish(self.label_widget) From de331447bec8b76a836b5e46a17de2ee02340f5b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 7 Aug 2020 19:21:35 +0200 Subject: [PATCH 270/947] added Websocket server to module imports --- pype/tools/tray/modules_imports.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/tools/tray/modules_imports.json b/pype/tools/tray/modules_imports.json index e9526dcddb..6a278840ea 100644 --- a/pype/tools/tray/modules_imports.json +++ b/pype/tools/tray/modules_imports.json @@ -54,5 +54,10 @@ "type": "module", "import_path": "pype.modules.adobe_communicator", "fromlist": ["pype", "modules"] + }, { + "title": "Websocket Server", + "type": "module", + "import_path": "pype.modules.websocket_server", + "fromlist": ["pype", "modules"] } ] From 72dc0246d7965f6911c123ecd205edb8a5982f7a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 10 Aug 2020 10:04:23 +0200 Subject: [PATCH 271/947] custom path wasn't working for delivery --- .../modules/ftrack/actions/action_delivery.py | 70 +++++++++++++------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index a2048222e5..231aebdf7a 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -11,7 +11,7 @@ from avalon.vendor import filelink from pype.api import Anatomy from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey +from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from pype.modules.ftrack.lib.io_nonsingleton import DbConnector @@ -81,13 +81,15 @@ class Delivery(BaseAction): anatomy = Anatomy(project_name) new_anatomies = [] first = None - for key in (anatomy.templates.get("delivery") or {}): - new_anatomies.append({ - "label": key, - "value": key - }) - if first is None: - first = key + for key, template in (anatomy.templates.get("delivery") or {}).items(): + # Use only keys with `{root}` or `{root[*]}` in value + if isinstance(template, str) and "{root" in template: + new_anatomies.append({ + "label": key, + "value": key + }) + if first is None: + first = key skipped = False # Add message if there are any common components @@ -243,7 +245,7 @@ class Delivery(BaseAction): version = entity["version"] parent = asset["parent"] - parent_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) + parent_mongo_id = parent["custom_attributes"].get(CUST_ATTR_ID_KEY) if parent_mongo_id: parent_mongo_id = ObjectId(parent_mongo_id) else: @@ -293,6 +295,20 @@ class Delivery(BaseAction): repres_to_deliver.append(repre) anatomy = Anatomy(project_name) + + format_dict = {} + if location_path: + location_path = location_path.replace("\\", "/") + root_names = anatomy.root_names_from_templates( + anatomy.templates["delivery"] + ) + if root_names is None: + format_dict["root"] = location_path + else: + format_dict["root"] = {} + for name in root_names: + format_dict["root"][name] = location_path + for repre in repres_to_deliver: # Get destination repre path anatomy_data = copy.deepcopy(repre["context"]) @@ -339,25 +355,33 @@ class Delivery(BaseAction): repre_path = self.path_from_represenation(repre, anatomy) # TODO add backup solution where root of path from component # is repalced with root - if not frame: - self.process_single_file( - repre_path, anatomy, anatomy_name, anatomy_data - ) + args = ( + repre_path, + anatomy, + anatomy_name, + anatomy_data, + format_dict + ) + if not frame: + self.process_single_file(*args) else: - self.process_sequence( - repre_path, anatomy, anatomy_name, anatomy_data - ) + self.process_sequence(*args) self.db_con.uninstall() return self.report() def process_single_file( - self, repre_path, anatomy, anatomy_name, anatomy_data + self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict ): anatomy_filled = anatomy.format(anatomy_data) - delivery_path = anatomy_filled["delivery"][anatomy_name] + if format_dict: + template_result = anatomy_filled["delivery"][anatomy_name] + delivery_path = template_result.rootless.format(**format_dict) + else: + delivery_path = anatomy_filled["delivery"][anatomy_name] + delivery_folder = os.path.dirname(delivery_path) if not os.path.exists(delivery_folder): os.makedirs(delivery_folder) @@ -365,7 +389,7 @@ class Delivery(BaseAction): self.copy_file(repre_path, delivery_path) def process_sequence( - self, repre_path, anatomy, anatomy_name, anatomy_data + self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict ): dir_path, file_name = os.path.split(str(repre_path)) @@ -408,8 +432,12 @@ class Delivery(BaseAction): anatomy_data["frame"] = frame_indicator anatomy_filled = anatomy.format(anatomy_data) - delivery_path = anatomy_filled["delivery"][anatomy_name] - print(delivery_path) + if format_dict: + template_result = anatomy_filled["delivery"][anatomy_name] + delivery_path = template_result.rootless.format(**format_dict) + else: + delivery_path = anatomy_filled["delivery"][anatomy_name] + delivery_folder = os.path.dirname(delivery_path) dst_head, dst_tail = delivery_path.split(frame_indicator) dst_padding = src_collection.padding From 3f5c686a6a4b35b7cc854fbc4babdb40104c03e2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Aug 2020 11:08:15 +0200 Subject: [PATCH 272/947] fix(sp): shots were assigned to wrong parent --- .../publish/collect_hierarchy.py | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index f518813bfe..7c66f21966 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -2,9 +2,6 @@ import pyblish.api import re import os from avalon import io -import queue -import threading - class CollectHierarchyInstance(pyblish.api.ContextPlugin): """Collecting hierarchy context from `parents` and `hierarchy` data @@ -20,11 +17,6 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): hosts = ["standalonepublisher"] families = ["shot"] - # multiprocessing - num_worker_threads = 10 - queue = queue.Queue() - threads = [] - # presets shot_rename_template = None shot_rename_search_patterns = None @@ -103,6 +95,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): instance.data["hierarchy"] = hierarchy instance.data["parents"] = parents + self.log.debug(f"Hierarchy: {hierarchy}") if self.shot_add_tasks: instance.data["tasks"] = self.shot_add_tasks @@ -115,38 +108,12 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): "task": "conform" }) - def queue_worker(self, worker_index): - while True: - instance = self.queue.get() - if instance is None: - break - self.processing_instance(instance, worker_index) - self.queue.task_done() - def process(self, context): - for i in range(self.num_worker_threads): - t = threading.Thread(target=self.queue_worker, args=(i,)) - t.start() - self.threads.append(t) - for instance in context: if instance.data["family"] in self.families: - self.queue.put(instance) + self.processing_instance(instance) - # block until all tasks are done - self.queue.join() - - self.log.info('stopping workers!') - - # stop workers - for i in range(self.num_worker_threads): - self.queue.put(None) - - for t in self.threads: - t.join() - - def processing_instance(self, instance, worker_index): - self.log.info(f"_ worker_index: {worker_index}") + def processing_instance(self, instance): self.log.info(f"_ instance: {instance}") # adding anatomyData for burnins instance.data["anatomyData"] = instance.context.data["anatomyData"] @@ -163,6 +130,10 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): self.create_hierarchy(instance) shot_name = instance.data["asset"] + self.log.debug(f"Shot Name: {shot_name}") + + if instance.data["hierarchy"] not in shot_name: + self.log.warning("wrong parent") label = f"{shot_name} ({frame_start}-{frame_end})" instance.data["label"] = label @@ -232,7 +203,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): handle_start = int(instance.data["handleStart"]) handle_end = int(instance.data["handleEnd"]) - in_info = {} # suppose that all instances are Shots From 63464d14b4449502f1b8df522bd699746cec961c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 13:22:18 +0200 Subject: [PATCH 273/947] splitted next_task_update into more parts --- .../ftrack/events/event_next_task_update.py | 249 +++++++++++++----- 1 file changed, 183 insertions(+), 66 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index dc1ab0a0d7..df2f3cd6ed 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -1,92 +1,209 @@ -import ftrack_api -from pype.modules.ftrack import BaseEvent import operator +import collections +from pype.modules.ftrack import BaseEvent class NextTaskUpdate(BaseEvent): + def filter_entities_info(self, event): + # Filter if event contain relevant data + entities_info = event["data"].get("entities") + if not entities_info: + return - def get_next_task(self, task, session): - parent = task['parent'] - # tasks = parent['tasks'] - tasks = parent['children'] + first_filtered_entities = [] + for entity_info in entities_info: + # Care only about tasks + if entity_info.get("entityType") != "task": + continue - def sort_types(types): - data = {} - for t in types: - data[t] = t.get('sort') + # Care only about changes of status + changes = entity_info.get("changes") or {} + statusid_changes = changes.get("statusid") or {} + if ( + statusid_changes.get("new") is None + or statusid_changes.get("old") is None + ): + continue - data = sorted(data.items(), key=operator.itemgetter(1)) - results = [] - for item in data: - results.append(item[0]) - return results + first_filtered_entities.append(entity_info) - types_sorted = sort_types(session.query('Type')) - next_types = None - for t in types_sorted: - if t['id'] == task['type_id']: - next_types = types_sorted[(types_sorted.index(t) + 1):] + status_ids = [ + entity_info["changes"]["statusid"]["new"] + for entity_info in first_filtered_entities + ] + statuses_by_id = self.get_statuses_by_id(status_ids=status_ids) - for nt in next_types: - for t in tasks: - if nt['id'] == t['type_id']: - return t + # Care only about tasks having status with state `Done` + filtered_entities = [] + for entity_info in first_filtered_entities: + status_id = entity_info["changes"]["statusid"]["new"] + status_entity = statuses_by_id[status_id] + if status_entity["state"]["name"].lower() == "done": + filtered_entities.append(entities_info) - return None + return filtered_entities + + def get_parents_by_id(self, session, entities_info): + parent_ids = [ + "\"{}\"".format(entity_info["parentId"]) + for entity_info in entities_info + ] + parent_entities = session.query( + "TypedContext where id in ({})".format(", ".join(parent_ids)) + ).all() + + return { + entity["id"]: entity + for entity in parent_entities + } + + def get_tasks_by_id(self, session, parent_ids): + joined_parent_ids = ",".join([ + "\"{}\"".format(parent_id) + for parent_id in parent_ids + ]) + task_entities = session.query( + "Task where parent_id in ({})".format(", ".join(joined_parent_ids)) + ).all() + + return { + entity["id"]: entity + for entity in task_entities + } + + def get_statuses_by_id(self, session, task_entities=None, status_ids=None): + if task_entities is None and status_ids is None: + return {} + + if status_ids is None: + status_ids = [] + for task_entity in task_entities: + status_ids.append(task_entities["status_id"]) + + status_entities = session.query( + "Status where id in ({})".format(", ".join(status_ids)) + ).all() + + return { + entity["id"]: entity + for entity in status_entities + } + + def get_sorted_task_types(self, session): + data = { + _type: _type.get("sort") + for _type in session.query("Type").all() + if _type.get("sort") is not None + } + + return [ + item[0] + for item in sorted(data.items(), key=operator.itemgetter(1)) + ] def launch(self, session, event): '''Propagates status from version to task when changed''' - # self.log.info(event) - # start of event procedure ---------------------------------- + entities_info = self.filter_entities_info(event) + if not entities_info: + return - for entity in event['data'].get('entities', []): - changes = entity.get('changes', None) - if changes is None: - continue - statusid_changes = changes.get('statusid', {}) - if ( - entity['entityType'] != 'task' or - 'statusid' not in (entity.get('keys') or []) or - statusid_changes.get('new', None) is None or - statusid_changes.get('old', None) is None - ): + parents_by_id = self.get_parents_by_id(session, entities_info) + tasks_by_id = self.get_tasks_by_id( + session, tuple(parents_by_id.keys()) + ) + + tasks_to_parent_id = collections.defaultdict(list) + for task_entity in tasks_by_id.values(): + tasks_to_parent_id[task_entity["parent_id"]].append(task_entity) + + statuses_by_id = self.get_statuses_by_id(session, tasks_by_id.values()) + + # Prepare all task types + sorted_task_types = self.get_sorted_task_types(session) + sorted_task_types_len = len(sorted_task_types) + + next_status_name = "Ready" + next_status = session.query( + "Status where name is \"{}\"".format(next_status_name) + ).first() + if not next_status: + self.log.warning("Couldn't find status with name \"{}\"".format( + next_status_name + )) + return + + for entity_info in entities_info: + parent_id = entities_info["parentId"] + task_id = entity_info["entityId"] + task_entity = tasks_by_id[task_id] + + all_same_type_taks_done = True + for parents_task in tasks_to_parent_id[parent_id]: + if ( + parents_task["id"] == task_id + or parents_task["type_id"] != task_entity["type_id"] + ): + continue + + parents_task_status = statuses_by_id[parents_task["status_id"]] + if parents_task_status["state"]["name"].lower() != "done": + all_same_type_taks_done = False + break + + if not all_same_type_taks_done: continue - task = session.get('Task', entity['entityId']) + from_idx = None + for idx, task_type in enumerate(sorted_task_types): + if task_type["id"] == task_entity["type_id"]: + from_idx = idx + 1 + break - status = session.get('Status', - entity['changes']['statusid']['new']) - state = status['state']['name'] + # Current task type is last in order + if from_idx >= sorted_task_types_len: + continue - next_task = self.get_next_task(task, session) + next_task_type_id = None + next_task_type_tasks = [] + for idx in range(from_idx, sorted_task_types_len): + next_task_type = sorted_task_types[idx] + for parents_task in tasks_to_parent_id[parent_id]: + if next_task_type_id is None: + if parents_task["type_id"] != next_task_type["id"]: + continue + next_task_type_id = next_task_type["id"] - # Setting next task to Ready, if on NOT READY - if next_task and state == 'Done': - if next_task['status']['name'].lower() == 'not ready': + if parents_task["type_id"] == next_task_type_id: + next_task_type_tasks.append(parents_task) - # Get path to task - path = task['name'] - for p in task['ancestors']: - path = p['name'] + '/' + path + if next_task_type_id is not None: + break - # Setting next task status - try: - query = 'Status where name is "{}"'.format('Ready') - status_to_set = session.query(query).one() - next_task['status'] = status_to_set - session.commit() - self.log.info(( - '>>> [ {} ] updated to [ Ready ]' - ).format(path)) - except Exception as e: - session.rollback() - self.log.warning(( - '!!! [ {} ] status couldnt be set: [ {} ]' - ).format(path, str(e)), exc_info=True) + for next_task_entity in next_task_type_tasks: + if next_task_entity["status"]["name"].lower() != "not ready": + continue + + ent_path = "/".join( + [ent["name"] for ent in next_task_entity["link"]] + ) + try: + next_task_entity["status"] = next_status + session.commit() + self.log.info( + "\"{}\" updated status to \"{}\"".format( + ent_path, next_status_name + ) + ) + except Exception: + session.rollback() + self.log.warning( + "\"{}\" status couldnt be set to \"{}\"".format( + ent_path, next_status_name + ), + exc_info=True + ) def register(session, plugins_presets): - '''Register plugin. Called when used as an plugin.''' - NextTaskUpdate(session, plugins_presets).register() From 151631596ad7170e24388360096cf5bbab993160 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 13:23:29 +0200 Subject: [PATCH 274/947] minor fixes --- .../ftrack/events/event_next_task_update.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index df2f3cd6ed..57315a1fed 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -4,7 +4,7 @@ from pype.modules.ftrack import BaseEvent class NextTaskUpdate(BaseEvent): - def filter_entities_info(self, event): + def filter_entities_info(self, session, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") if not entities_info: @@ -31,7 +31,9 @@ class NextTaskUpdate(BaseEvent): entity_info["changes"]["statusid"]["new"] for entity_info in first_filtered_entities ] - statuses_by_id = self.get_statuses_by_id(status_ids=status_ids) + statuses_by_id = self.get_statuses_by_id( + session, status_ids=status_ids + ) # Care only about tasks having status with state `Done` filtered_entities = [] @@ -39,7 +41,7 @@ class NextTaskUpdate(BaseEvent): status_id = entity_info["changes"]["statusid"]["new"] status_entity = statuses_by_id[status_id] if status_entity["state"]["name"].lower() == "done": - filtered_entities.append(entities_info) + filtered_entities.append(entity_info) return filtered_entities @@ -63,7 +65,7 @@ class NextTaskUpdate(BaseEvent): for parent_id in parent_ids ]) task_entities = session.query( - "Task where parent_id in ({})".format(", ".join(joined_parent_ids)) + "Task where parent_id in ({})".format(joined_parent_ids) ).all() return { @@ -78,7 +80,10 @@ class NextTaskUpdate(BaseEvent): if status_ids is None: status_ids = [] for task_entity in task_entities: - status_ids.append(task_entities["status_id"]) + status_ids.append(task_entity["status_id"]) + + if not status_ids: + return {} status_entities = session.query( "Status where id in ({})".format(", ".join(status_ids)) @@ -104,7 +109,7 @@ class NextTaskUpdate(BaseEvent): def launch(self, session, event): '''Propagates status from version to task when changed''' - entities_info = self.filter_entities_info(event) + entities_info = self.filter_entities_info(session, event) if not entities_info: return @@ -134,7 +139,7 @@ class NextTaskUpdate(BaseEvent): return for entity_info in entities_info: - parent_id = entities_info["parentId"] + parent_id = entity_info["parentId"] task_id = entity_info["entityId"] task_entity = tasks_by_id[task_id] From 0931b067fd7da8a5598257dcf9a791cb460b19e7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Aug 2020 14:26:00 +0200 Subject: [PATCH 275/947] fix(ca): half frame extension --- pype/plugins/standalonepublisher/publish/extract_shot_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py index e0b6196279..8c626b277c 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot_data.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -47,7 +47,6 @@ class ExtractShotData(pype.api.Extractor): if ext in ".wav": start += 0.5 - dur += 0.5 args = [ ffmpeg_path, @@ -62,7 +61,8 @@ class ExtractShotData(pype.api.Extractor): elif ext in ".wav": args.extend([ "-vn -acodec pcm_s16le", - "-ar 44100 -ac 2"]) + "-ar 48000 -ac 2" + ]) # add output path args.append(f"\"{clip_trimed_path}\"") From 0c67a85ce7b1558d2d236d2f3023023668282dc1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 14:28:58 +0200 Subject: [PATCH 276/947] moved few steps --- pype/modules/ftrack/events/event_next_task_update.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index 57315a1fed..0f84ed4b44 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -124,10 +124,6 @@ class NextTaskUpdate(BaseEvent): statuses_by_id = self.get_statuses_by_id(session, tasks_by_id.values()) - # Prepare all task types - sorted_task_types = self.get_sorted_task_types(session) - sorted_task_types_len = len(sorted_task_types) - next_status_name = "Ready" next_status = session.query( "Status where name is \"{}\"".format(next_status_name) @@ -159,6 +155,10 @@ class NextTaskUpdate(BaseEvent): if not all_same_type_taks_done: continue + # Prepare all task types + sorted_task_types = self.get_sorted_task_types(session) + sorted_task_types_len = len(sorted_task_types) + from_idx = None for idx, task_type in enumerate(sorted_task_types): if task_type["id"] == task_entity["type_id"]: @@ -166,7 +166,7 @@ class NextTaskUpdate(BaseEvent): break # Current task type is last in order - if from_idx >= sorted_task_types_len: + if from_idx is None or from_idx >= sorted_task_types_len: continue next_task_type_id = None From ca165f6139d72938983ac4f74bf601996a1853ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 16:25:23 +0200 Subject: [PATCH 277/947] blocked statuses are ignored when done statuses are checked --- pype/modules/ftrack/events/event_next_task_update.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index 0f84ed4b44..2df3800d8a 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -148,7 +148,12 @@ class NextTaskUpdate(BaseEvent): continue parents_task_status = statuses_by_id[parents_task["status_id"]] - if parents_task_status["state"]["name"].lower() != "done": + low_state_name = parents_task_status["state"]["name"].lower() + # Skip if task's status is in blocked state (e.g. Omitted) + if low_state_name != "blocked": + continue + + if low_state_name != "done": all_same_type_taks_done = False break From a3e8828ad1759b8ed943d5edb1fcb2e45d79388e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Aug 2020 16:39:06 +0200 Subject: [PATCH 278/947] hound(sp): suggestions --- pype/plugins/global/publish/extract_burnin.py | 3 --- pype/plugins/global/publish/extract_review.py | 3 --- .../publish/collect_hierarchy.py | 1 + .../publish/extract_shot_data.py | 1 - .../publish/validate_clips.py | 24 ------------------- 5 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 pype/plugins/standalonepublisher/publish/validate_clips.py diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 08aa2c2d05..e1508b9131 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -49,9 +49,6 @@ class ExtractBurnin(pype.api.Extractor): fields = None def process(self, instance): - representation = instance.data["representations"] - self.log.debug(f"_ representation: {representation}") - # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: instance_label = ( diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 46de467b2c..a16c3ce256 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -50,9 +50,6 @@ class ExtractReview(pyblish.api.InstancePlugin): to_height = 1080 def process(self, instance): - representation = instance.data["representations"] - self.log.debug(f"_ representation: {representation}") - # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: instance_label = ( diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index 7c66f21966..b5d37d0a6c 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -3,6 +3,7 @@ import re import os from avalon import io + class CollectHierarchyInstance(pyblish.api.ContextPlugin): """Collecting hierarchy context from `parents` and `hierarchy` data present in `clip` family instances coming from the request json data file diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py index 8c626b277c..6cbc2c7882 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot_data.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -1,7 +1,6 @@ import os import clique import pype.api -import pype.lib as plib from pprint import pformat diff --git a/pype/plugins/standalonepublisher/publish/validate_clips.py b/pype/plugins/standalonepublisher/publish/validate_clips.py deleted file mode 100644 index 35b81da5c1..0000000000 --- a/pype/plugins/standalonepublisher/publish/validate_clips.py +++ /dev/null @@ -1,24 +0,0 @@ - # Check for clips with the same range - # this is for testing if any vertically neighbouring - # clips has been already processed - clip_matching_with_range = next( - (k for k, v in context.data["assetsShared"].items() - if (v.get("_clipIn", 0) == clip_in) - and (v.get("_clipOut", 0) == clip_out) - ), False) - - # check if clip name is the same in matched - # vertically neighbouring clip - # if it is then it is correct and resent variable to False - # not to be rised wrong name exception - if asset in str(clip_matching_with_range): - clip_matching_with_range = False - - # rise wrong name exception if found one - assert (not clip_matching_with_range), ( - "matching clip: {asset}" - " timeline range ({clip_in}:{clip_out})" - " conflicting with {clip_matching_with_range}" - " >> rename any of clips to be the same as the other <<" - ).format( - **locals()) From 0b4e01eca72e4d95021ad5ea012a491eef0e251d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 10 Aug 2020 17:16:50 +0200 Subject: [PATCH 279/947] bump version --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index ddcf716b76..d70304e62c 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.11.3" +__version__ = "2.11.4" From 1db9d9e247d2fe1259350c8dc14b2446720a6909 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 10 Aug 2020 17:09:17 +0100 Subject: [PATCH 280/947] fix delivery action variable and create directories --- pype/modules/ftrack/actions/action_delivery.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 231aebdf7a..7dbb7c65e8 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -11,7 +11,7 @@ from avalon.vendor import filelink from pype.api import Anatomy from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY +from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey from pype.modules.ftrack.lib.io_nonsingleton import DbConnector @@ -228,12 +228,7 @@ class Delivery(BaseAction): if location_path: location_path = os.path.normpath(location_path) if not os.path.exists(location_path): - return { - "success": False, - "message": ( - "Entered location path does not exists. \"{}\"" - ).format(location_path) - } + os.makedirs(location_path) self.db_con.install() self.db_con.Session["AVALON_PROJECT"] = project_name @@ -245,7 +240,7 @@ class Delivery(BaseAction): version = entity["version"] parent = asset["parent"] - parent_mongo_id = parent["custom_attributes"].get(CUST_ATTR_ID_KEY) + parent_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) if parent_mongo_id: parent_mongo_id = ObjectId(parent_mongo_id) else: From a27eab6049c3cb910e53eda2eb0de48249ffb653 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 21:41:33 +0200 Subject: [PATCH 281/947] init commit --- pype/tools/launcher/__init__.py | 10 + pype/tools/launcher/__main__.py | 5 + pype/tools/launcher/actions.py | 95 ++++ pype/tools/launcher/app.py | 717 ++++++++++++++++++++++++++++++ pype/tools/launcher/flickcharm.py | 305 +++++++++++++ pype/tools/launcher/lib.py | 67 +++ pype/tools/launcher/models.py | 292 ++++++++++++ pype/tools/launcher/widgets.py | 419 +++++++++++++++++ 8 files changed, 1910 insertions(+) create mode 100644 pype/tools/launcher/__init__.py create mode 100644 pype/tools/launcher/__main__.py create mode 100644 pype/tools/launcher/actions.py create mode 100644 pype/tools/launcher/app.py create mode 100644 pype/tools/launcher/flickcharm.py create mode 100644 pype/tools/launcher/lib.py create mode 100644 pype/tools/launcher/models.py create mode 100644 pype/tools/launcher/widgets.py diff --git a/pype/tools/launcher/__init__.py b/pype/tools/launcher/__init__.py new file mode 100644 index 0000000000..3b88ebe984 --- /dev/null +++ b/pype/tools/launcher/__init__.py @@ -0,0 +1,10 @@ + +from .app import ( + show, + cli +) + +__all__ = [ + "show", + "cli", +] diff --git a/pype/tools/launcher/__main__.py b/pype/tools/launcher/__main__.py new file mode 100644 index 0000000000..50642c46cd --- /dev/null +++ b/pype/tools/launcher/__main__.py @@ -0,0 +1,5 @@ +from app import cli + +if __name__ == '__main__': + import sys + sys.exit(cli(sys.argv[1:])) diff --git a/pype/tools/launcher/actions.py b/pype/tools/launcher/actions.py new file mode 100644 index 0000000000..2a2e2ab0f0 --- /dev/null +++ b/pype/tools/launcher/actions.py @@ -0,0 +1,95 @@ +import os +import importlib + +from avalon import api, lib + + +class ProjectManagerAction(api.Action): + name = "projectmanager" + label = "Project Manager" + icon = "gear" + group = "Test" + order = 999 # at the end + + def is_compatible(self, session): + return "AVALON_PROJECT" in session + + def process(self, session, **kwargs): + return lib.launch(executable="python", + args=["-u", "-m", "avalon.tools.projectmanager", + session['AVALON_PROJECT']]) + + +class LoaderAction(api.Action): + name = "loader" + label = "Loader" + icon = "cloud-download" + order = 998 # at the end + group = "Test" + + def is_compatible(self, session): + return "AVALON_PROJECT" in session + + def process(self, session, **kwargs): + return lib.launch(executable="python", + args=["-u", "-m", "avalon.tools.cbloader", + session['AVALON_PROJECT']]) + + +class LoaderLibrary(api.Action): + name = "loader_os" + label = "Library Loader" + icon = "book" + order = 997 # at the end + + def is_compatible(self, session): + return True + + def process(self, session, **kwargs): + return lib.launch(executable="python", + args=["-u", "-m", "avalon.tools.libraryloader"]) + + +def register_default_actions(): + """Register default actions for Launcher""" + api.register_plugin(api.Action, ProjectManagerAction) + api.register_plugin(api.Action, LoaderAction) + api.register_plugin(api.Action, LoaderLibrary) + + +def register_config_actions(): + """Register actions from the configuration for Launcher""" + + module_name = os.environ["AVALON_CONFIG"] + config = importlib.import_module(module_name) + if not hasattr(config, "register_launcher_actions"): + print("Current configuration `%s` has no 'register_launcher_actions'" + % config.__name__) + return + + config.register_launcher_actions() + + +def register_environment_actions(): + """Register actions from AVALON_ACTIONS for Launcher.""" + + paths = os.environ.get("AVALON_ACTIONS") + if not paths: + return + + for path in paths.split(os.pathsep): + api.register_plugin_path(api.Action, path) + + # Run "register" if found. + for module in lib.modules_from_path(path): + if "register" not in dir(module): + continue + + try: + module.register() + except Exception as e: + print( + "Register method in {0} failed: {1}".format( + module, str(e) + ) + ) diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py new file mode 100644 index 0000000000..8bce705fc9 --- /dev/null +++ b/pype/tools/launcher/app.py @@ -0,0 +1,717 @@ +import sys +import copy + +from avalon.vendor.Qt import QtWidgets, QtCore, QtGui +from avalon import io, api, style + +from avalon.tools import lib as tools_lib +from avalon.tools.widgets import AssetWidget +from avalon.vendor import qtawesome +from .models import ProjectModel +from .widgets import ( + ProjectBar, ActionBar, TasksWidget, ActionHistory, SlidePageWidget +) + +from .flickcharm import FlickCharm + +module = sys.modules[__name__] +module.window = None + + +class IconListView(QtWidgets.QListView): + """Styled ListView that allows to toggle between icon and list mode. + + Toggling between the two modes is done by Right Mouse Click. + + """ + + IconMode = 0 + ListMode = 1 + + def __init__(self, parent=None, mode=ListMode): + super(IconListView, self).__init__(parent=parent) + + # Workaround for scrolling being super slow or fast when + # toggling between the two visual modes + self.setVerticalScrollMode(self.ScrollPerPixel) + + self._mode = 0 + self.set_mode(mode) + + def set_mode(self, mode): + if mode == self.IconMode: + self.setViewMode(QtWidgets.QListView.IconMode) + self.setResizeMode(QtWidgets.QListView.Adjust) + self.setWrapping(True) + self.setWordWrap(True) + self.setGridSize(QtCore.QSize(151, 90)) + self.setIconSize(QtCore.QSize(50, 50)) + self.setSpacing(0) + self.setAlternatingRowColors(False) + + self.setStyleSheet(""" + QListView { + font-size: 11px; + border: 0px; + padding: 0px; + margin: 0px; + + } + + QListView::item { + margin-top: 6px; + /* Won't work without borders set */ + border: 0px; + } + + /* For icon only */ + QListView::icon { + top: 3px; + } + """) + + self.verticalScrollBar().setSingleStep(30) + + elif self.ListMode: + self.setStyleSheet("") # clear stylesheet + self.setViewMode(QtWidgets.QListView.ListMode) + self.setResizeMode(QtWidgets.QListView.Adjust) + self.setWrapping(False) + self.setWordWrap(False) + self.setIconSize(QtCore.QSize(20, 20)) + self.setGridSize(QtCore.QSize(100, 25)) + self.setSpacing(0) + self.setAlternatingRowColors(False) + + self.verticalScrollBar().setSingleStep(33.33) + + self._mode = mode + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + self.set_mode(int(not self._mode)) + return super(IconListView, self).mousePressEvent(event) + + +class ProjectsPanel(QtWidgets.QWidget): + """Projects Page""" + + project_clicked = QtCore.Signal(str) + + def __init__(self, parent=None): + super(ProjectsPanel, self).__init__(parent=parent) + + layout = QtWidgets.QVBoxLayout(self) + + io.install() + view = IconListView(parent=self) + view.setSelectionMode(QtWidgets.QListView.NoSelection) + flick = FlickCharm(parent=self) + flick.activateOn(view) + model = ProjectModel() + model.hide_invisible = True + model.refresh() + view.setModel(model) + + layout.addWidget(view) + + view.clicked.connect(self.on_clicked) + + self.model = model + self.view = view + + def on_clicked(self, index): + if index.isValid(): + project = index.data(QtCore.Qt.DisplayRole) + self.project_clicked.emit(project) + + +class AssetsPanel(QtWidgets.QWidget): + """Assets page""" + + back_clicked = QtCore.Signal() + + def __init__(self, parent=None): + super(AssetsPanel, self).__init__(parent=parent) + + # project bar + project_bar = QtWidgets.QWidget() + layout = QtWidgets.QHBoxLayout(project_bar) + layout.setSpacing(4) + back = QtWidgets.QPushButton("<") + back.setFixedWidth(25) + back.setFixedHeight(23) + projects = ProjectBar() + projects.layout().setContentsMargins(0, 0, 0, 0) + layout.addWidget(back) + layout.addWidget(projects) + + # assets + _assets_widgets = QtWidgets.QWidget() + _assets_widgets.setContentsMargins(0, 0, 0, 0) + assets_layout = QtWidgets.QVBoxLayout(_assets_widgets) + assets_widgets = AssetWidget() + + # Make assets view flickable + flick = FlickCharm(parent=self) + flick.activateOn(assets_widgets.view) + assets_widgets.view.setVerticalScrollMode( + assets_widgets.view.ScrollPerPixel + ) + assets_layout.addWidget(assets_widgets) + + # tasks + tasks_widgets = TasksWidget() + body = QtWidgets.QSplitter() + body.setContentsMargins(0, 0, 0, 0) + body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + body.setOrientation(QtCore.Qt.Horizontal) + body.addWidget(_assets_widgets) + body.addWidget(tasks_widgets) + body.setStretchFactor(0, 100) + body.setStretchFactor(1, 65) + + # main layout + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(project_bar) + layout.addWidget(body) + + self.data = { + "model": { + "projects": projects, + "assets": assets_widgets, + "tasks": tasks_widgets + }, + } + + # signals + projects.project_changed.connect(self.on_project_changed) + assets_widgets.selection_changed.connect(self.asset_changed) + back.clicked.connect(self.back_clicked) + + # Force initial refresh for the assets since we might not be + # trigging a Project switch if we click the project that was set + # prior to launching the Launcher + # todo: remove this behavior when AVALON_PROJECT is not required + assets_widgets.refresh() + + def set_project(self, project): + + projects = self.data["model"]["projects"] + + before = projects.get_current_project() + projects.set_project(project) + if project == before: + # Force a refresh on the assets if the project hasn't changed + self.data["model"]["assets"].refresh() + + def asset_changed(self): + tools_lib.schedule(self.on_asset_changed, 0.05, + channel="assets") + + def on_project_changed(self): + + project = self.data["model"]["projects"].get_current_project() + + api.Session["AVALON_PROJECT"] = project + self.data["model"]["assets"].refresh() + + # Force asset change callback to ensure tasks are correctly reset + self.asset_changed() + + def on_asset_changed(self): + """Callback on asset selection changed + + This updates the task view. + + """ + + print("Asset changed..") + + tasks = self.data["model"]["tasks"] + assets = self.data["model"]["assets"] + + asset = assets.get_active_asset_document() + if asset: + tasks.set_asset(asset["_id"]) + else: + tasks.set_asset(None) + + def _get_current_session(self): + + tasks = self.data["model"]["tasks"] + assets = self.data["model"]["assets"] + + asset = assets.get_active_asset_document() + session = copy.deepcopy(api.Session) + + # Clear some values that we are about to collect if available + session.pop("AVALON_SILO", None) + session.pop("AVALON_ASSET", None) + session.pop("AVALON_TASK", None) + + if asset: + session["AVALON_ASSET"] = asset["name"] + + silo = asset.get("silo") + if silo: + session["AVALON_SILO"] = silo + + task = tasks.get_current_task() + if task: + session["AVALON_TASK"] = task + + return session + + +class Window(QtWidgets.QDialog): + """Launcher interface""" + + def __init__(self, parent=None): + super(Window, self).__init__(parent) + + self.setWindowTitle("Launcher") + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + + # Allow minimize + self.setWindowFlags( + self.windowFlags() | QtCore.Qt.WindowMinimizeButtonHint + ) + + project_panel = ProjectsPanel() + asset_panel = AssetsPanel() + + pages = SlidePageWidget() + pages.addWidget(project_panel) + pages.addWidget(asset_panel) + + # actions + actions = ActionBar() + + # statusbar + statusbar = QtWidgets.QWidget() + message = QtWidgets.QLabel() + message.setFixedHeight(15) + action_history = ActionHistory() + action_history.setStatusTip("Show Action History") + layout = QtWidgets.QHBoxLayout(statusbar) + layout.addWidget(message) + layout.addWidget(action_history) + + # Vertically split Pages and Actions + body = QtWidgets.QSplitter() + body.setContentsMargins(0, 0, 0, 0) + body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding) + body.setOrientation(QtCore.Qt.Vertical) + body.addWidget(pages) + body.addWidget(actions) + + # Set useful default sizes and set stretch + # for the pages so that is the only one that + # stretches on UI resize. + body.setStretchFactor(0, 10) + body.setSizes([580, 160]) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(body) + layout.addWidget(statusbar) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + + self.data = { + "label": { + "message": message, + }, + "pages": { + "project": project_panel, + "asset": asset_panel + }, + "model": { + "actions": actions, + "action_history": action_history + }, + } + + self.pages = pages + self._page = 0 + + # signals + actions.action_clicked.connect(self.on_action_clicked) + action_history.trigger_history.connect(self.on_history_action) + project_panel.project_clicked.connect(self.on_project_clicked) + asset_panel.back_clicked.connect(self.on_back_clicked) + + # Add some signals to propagate from the asset panel + for signal in [ + asset_panel.data["model"]["projects"].project_changed, + asset_panel.data["model"]["assets"].selection_changed, + asset_panel.data["model"]["tasks"].task_changed + ]: + signal.connect(self.on_session_changed) + + # todo: Simplify this callback connection + asset_panel.data["model"]["projects"].project_changed.connect( + self.on_project_changed + ) + + self.resize(520, 740) + + def set_page(self, page): + + current = self.pages.currentIndex() + if current == page and self._page == page: + return + + direction = "right" if page > current else "left" + self._page = page + self.pages.slide_view(page, direction=direction) + + def refresh(self): + asset = self.data["pages"]["asset"] + asset.data["model"]["assets"].refresh() + self.refresh_actions() + + def echo(self, message): + widget = self.data["label"]["message"] + widget.setText(str(message)) + + QtCore.QTimer.singleShot(5000, lambda: widget.setText("")) + + print(message) + + def on_project_changed(self): + project_name = self.data["pages"]["asset"].data["model"]["projects"].get_current_project() + io.Session["AVALON_PROJECT"] = project_name + + # Update the Action plug-ins available for the current project + actions_model = self.data["model"]["actions"].model + actions_model.discover() + + def on_session_changed(self): + self.refresh_actions() + + def refresh_actions(self, delay=1): + tools_lib.schedule(self.on_refresh_actions, delay) + + def on_project_clicked(self, project): + io.Session["AVALON_PROJECT"] = project + asset_panel = self.data["pages"]["asset"] + asset_panel.data["model"]["projects"].refresh() # Refresh projects + asset_panel.set_project(project) + self.set_page(1) + self.refresh_actions() + + def on_back_clicked(self): + + self.set_page(0) + self.data["pages"]["project"].model.refresh() # Refresh projects + self.refresh_actions() + + def on_refresh_actions(self): + session = self.get_current_session() + + actions = self.data["model"]["actions"] + actions.model.set_session(session) + actions.model.refresh() + + def on_action_clicked(self, action): + self.echo("Running action: %s" % action.name) + self.run_action(action) + + def on_history_action(self, history_data): + action, session = history_data + app = QtWidgets.QApplication.instance() + modifiers = app.keyboardModifiers() + + is_control_down = QtCore.Qt.ControlModifier & modifiers + if is_control_down: + # User is holding control, rerun the action + self.run_action(action, session=session) + else: + # Revert to that "session" location + self.set_session(session) + + def get_current_session(self): + + index = self._page + if index == 1: + # Assets page + return self.data["pages"]["asset"]._get_current_session() + + else: + session = copy.deepcopy(api.Session) + + # Remove some potential invalid session values + # that we know are not set when not browsing in + # a project. + session.pop("AVALON_PROJECT", None) + session.pop("AVALON_ASSET", None) + session.pop("AVALON_SILO", None) + session.pop("AVALON_TASK", None) + + return session + + def run_action(self, action, session=None): + + if session is None: + session = self.get_current_session() + + # Add to history + history = self.data["model"]["action_history"] + history.add_action(action, session) + + # Process the Action + action().process(session) + + def set_session(self, session): + + panel = self.data["pages"]["asset"] + + project = session.get("AVALON_PROJECT") + silo = session.get("AVALON_SILO") + asset = session.get("AVALON_ASSET") + task = session.get("AVALON_TASK") + + if project: + + # Force the "in project" view. + self.pages.slide_view(1, direction="right") + + projects = panel.data["model"]["projects"] + index = projects.view.findText(project) + if index >= 0: + projects.view.setCurrentIndex(index) + + if silo: + panel.data["model"]["assets"].set_silo(silo) + + if asset: + panel.data["model"]["assets"].select_assets([asset]) + + if task: + panel.on_asset_changed() # requires a forced refresh first + panel.data["model"]["tasks"].select_task(task) + + +class Application(QtWidgets.QApplication): + + def __init__(self, *args): + super(Application, self).__init__(*args) + + # Set app icon + icon_path = tools_lib.resource("icons", "png", "avalon-logo-16.png") + icon = QtGui.QIcon(icon_path) + + self.setWindowIcon(icon) + + # Toggles + self.toggles = {"autoHide": False} + + # Timers + keep_visible = QtCore.QTimer(self) + keep_visible.setInterval(100) + keep_visible.setSingleShot(True) + + timers = {"keepVisible": keep_visible} + + tray = QtWidgets.QSystemTrayIcon(icon) + tray.setToolTip("Avalon Launcher") + + # Signals + tray.activated.connect(self.on_tray_activated) + self.aboutToQuit.connect(self.on_quit) + + menu = self.build_menu() + tray.setContextMenu(menu) + tray.show() + + tray.showMessage("Avalon", "Launcher started.") + + # Don't close the app when we close the log window. + # self.setQuitOnLastWindowClosed(False) + + self.focusChanged.connect(self.on_focus_changed) + + window = Window() + window.setAttribute(QtCore.Qt.WA_DeleteOnClose, False) + + self.timers = timers + self._tray = tray + self._window = window + + # geometry = self.calculate_window_geometry(window) + # window.setGeometry(geometry) + + def show(self): + """Show the primary GUI + + This also activates the window and deals with platform-differences. + + """ + + self._window.show() + self._window.raise_() + self._window.activateWindow() + + self.timers["keepVisible"].start() + + def on_tray_activated(self, reason): + if self._window.isVisible(): + self._window.hide() + + elif reason == QtWidgets.QSystemTrayIcon.Trigger: + self.show() + + def on_focus_changed(self, old, new): + """Respond to window losing focus""" + window = new + keep_visible = self.timers["keepVisible"].isActive() + self._window.hide() if (self.toggles["autoHide"] and + not window and + not keep_visible) else None + + def on_autohide_changed(self, auto_hide): + """Respond to changes to auto-hide + + Auto-hide is changed in the UI and determines whether or not + the UI hides upon losing focus. + + """ + + self.toggles["autoHide"] = auto_hide + self.echo("Hiding when losing focus" if auto_hide else "Stays visible") + + def on_quit(self): + """Respond to the application quitting""" + self._tray.hide() + + def build_menu(self): + """Build the right-mouse context menu for the tray icon""" + menu = QtWidgets.QMenu() + + icon = qtawesome.icon("fa.eye", color=style.colors.default) + open = QtWidgets.QAction(icon, "Open", self) + open.triggered.connect(self.show) + + def toggle(): + self.on_autohide_changed(not self.toggles['autoHide']) + + keep_open = QtWidgets.QAction("Keep open", self) + keep_open.setCheckable(True) + keep_open.setChecked(not self.toggles['autoHide']) + keep_open.triggered.connect(toggle) + + quit = QtWidgets.QAction("Quit", self) + quit.triggered.connect(self.quit) + + menu.setStyleSheet(""" + QMenu { + padding: 0px; + margin: 0px; + } + """) + + for action in [open, keep_open, quit]: + menu.addAction(action) + + return menu + + def calculate_window_geometry(self, window): + """Respond to status changes + + On creation, align window with where the tray icon is + located. For example, if the tray icon is in the upper + right corner of the screen, then this is where the + window is supposed to appear. + + Arguments: + status (int): Provided by Qt, the status flag of + loading the input file. + + """ + + tray_x = self._tray.geometry().x() + tray_y = self._tray.geometry().y() + + width = window.width() + width = max(width, window.minimumWidth()) + + height = window.height() + height = max(height, window.sizeHint().height()) + + desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() + screen_geometry = window.geometry() + + screen_width = screen_geometry.width() + screen_height = screen_geometry.height() + + # Calculate width and height of system tray + systray_width = screen_geometry.width() - desktop_geometry.width() + systray_height = screen_geometry.height() - desktop_geometry.height() + + padding = 10 + + x = screen_width - width + y = screen_height - height + + if tray_x < (screen_width / 2): + x = 0 + systray_width + padding + else: + x -= systray_width + padding + + if tray_y < (screen_height / 2): + y = 0 + systray_height + padding + else: + y -= systray_height + padding + + return QtCore.QRect(x, y, width, height) + + +def show(root=None, debug=False, parent=None): + """Display Loader GUI + + Arguments: + debug (bool, optional): Run loader in debug-mode, + defaults to False + parent (QtCore.QObject, optional): When provided parent the interface + to this QObject. + + """ + + app = Application(sys.argv) + app.setStyleSheet(style.load_stylesheet()) + + # Show the window on launch + app.show() + + app.exec_() + + +def cli(args): + import argparse + parser = argparse.ArgumentParser() + #parser.add_argument("project") + + args = parser.parse_args(args) + #project = args.project + + import launcher.actions as actions + print("Registering default actions..") + actions.register_default_actions() + print("Registering config actions..") + actions.register_config_actions() + print("Registering environment actions..") + actions.register_environment_actions() + io.install() + + #api.Session["AVALON_PROJECT"] = project + + import traceback + sys.excepthook = lambda typ, val, tb: traceback.print_last() + + show() diff --git a/pype/tools/launcher/flickcharm.py b/pype/tools/launcher/flickcharm.py new file mode 100644 index 0000000000..b4dd69be6c --- /dev/null +++ b/pype/tools/launcher/flickcharm.py @@ -0,0 +1,305 @@ +""" +This based on the flickcharm-python code from: + https://code.google.com/archive/p/flickcharm-python/ + +Which states: + This is a Python (PyQt) port of Ariya Hidayat's elegant FlickCharm + hack which adds kinetic scrolling to any scrollable Qt widget. + + Licensed under GNU GPL version 2 or later. + +It has been altered to fix edge cases where clicks and drags would not +propagate correctly under some conditions. It also allows a small "dead zone" +threshold in which it will still propagate the user pressed click if he or she +travelled only very slightly with the cursor. + +""" + +import copy +import sys +from Qt import QtWidgets, QtCore, QtGui + + +class FlickData(object): + Steady = 0 + Pressed = 1 + ManualScroll = 2 + AutoScroll = 3 + Stop = 4 + + def __init__(self): + self.state = FlickData.Steady + self.widget = None + self.pressPos = QtCore.QPoint(0, 0) + self.offset = QtCore.QPoint(0, 0) + self.dragPos = QtCore.QPoint(0, 0) + self.speed = QtCore.QPoint(0, 0) + self.travelled = 0 + self.ignored = [] + + +class FlickCharm(QtCore.QObject): + """Make scrollable widgets flickable. + + For example: + charm = FlickCharm() + charm.activateOn(widget) + + It can `activateOn` multiple widgets with a single FlickCharm instance. + Be aware that the FlickCharm object must be kept around for it not + to get garbage collected and losing the flickable behavior. + + Flick away! + + """ + + def __init__(self, parent=None): + super(FlickCharm, self).__init__(parent=parent) + + self.flickData = {} + self.ticker = QtCore.QBasicTimer() + + # The flick button to use + self.button = QtCore.Qt.LeftButton + + # The time taken per update tick of flicking behavior + self.tick_time = 20 + + # Allow a item click/press directly when AutoScroll is slower than + # this threshold velocity + self.click_in_autoscroll_threshold = 10 + + # Allow an item click/press to propagate as opposed to scrolling + # when the cursor travelled less than this amount of pixels + # Note: back & forth motion increases the value too + self.travel_threshold = 20 + + self.max_speed = 64 # max scroll speed + self.drag = 1 # higher drag will stop autoscroll faster + + def activateOn(self, widget): + viewport = widget.viewport() + viewport.installEventFilter(self) + widget.installEventFilter(self) + self.flickData[viewport] = FlickData() + self.flickData[viewport].widget = widget + self.flickData[viewport].state = FlickData.Steady + + def deactivateFrom(self, widget): + + viewport = widget.viewport() + viewport.removeEventFilter(self) + widget.removeEventFilter(self) + self.flickData.pop(viewport) + + def eventFilter(self, obj, event): + + if not obj.isWidgetType(): + return False + + eventType = event.type() + if eventType != QtCore.QEvent.MouseButtonPress and \ + eventType != QtCore.QEvent.MouseButtonRelease and \ + eventType != QtCore.QEvent.MouseMove: + return False + + if event.modifiers() != QtCore.Qt.NoModifier: + return False + + if obj not in self.flickData: + return False + + data = self.flickData[obj] + found, newIgnored = removeAll(data.ignored, event) + if found: + data.ignored = newIgnored + return False + + if data.state == FlickData.Steady: + if eventType == QtCore.QEvent.MouseButtonPress: + if event.buttons() == self.button: + self._set_press_pos_and_offset(event, data) + data.state = FlickData.Pressed + return True + + elif data.state == FlickData.Pressed: + if eventType == QtCore.QEvent.MouseButtonRelease: + # User didn't actually scroll but clicked in + # the widget. Let the original press and release + # event be evaluated on the Widget + data.state = FlickData.Steady + event1 = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, + data.pressPos, + QtCore.Qt.LeftButton, + QtCore.Qt.LeftButton, + QtCore.Qt.NoModifier) + # Copy the current event + event2 = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, + event.pos(), + event.button(), + event.buttons(), + event.modifiers()) + data.ignored.append(event1) + data.ignored.append(event2) + QtWidgets.QApplication.postEvent(obj, event1) + QtWidgets.QApplication.postEvent(obj, event2) + return True + elif eventType == QtCore.QEvent.MouseMove: + data.state = FlickData.ManualScroll + data.dragPos = QtGui.QCursor.pos() + if not self.ticker.isActive(): + self.ticker.start(self.tick_time, self) + return True + + elif data.state == FlickData.ManualScroll: + if eventType == QtCore.QEvent.MouseMove: + pos = event.pos() + delta = pos - data.pressPos + data.travelled += delta.manhattanLength() + setScrollOffset(data.widget, data.offset - delta) + return True + elif eventType == QtCore.QEvent.MouseButtonRelease: + + if data.travelled <= self.travel_threshold: + # If the user travelled less than the threshold + # don't go into autoscroll mode but assume the user + # intended to click instead + return self._propagate_click(obj, event, data) + + data.state = FlickData.AutoScroll + return True + + elif data.state == FlickData.AutoScroll: + if eventType == QtCore.QEvent.MouseButtonPress: + + # Allow pressing when auto scroll is already slower than + # the click in autoscroll threshold + velocity = data.speed.manhattanLength() + if velocity <= self.click_in_autoscroll_threshold: + self._set_press_pos_and_offset(event, data) + data.state = FlickData.Pressed + else: + data.state = FlickData.Stop + + data.speed = QtCore.QPoint(0, 0) + return True + elif eventType == QtCore.QEvent.MouseButtonRelease: + data.state = FlickData.Steady + data.speed = QtCore.QPoint(0, 0) + return True + + elif data.state == FlickData.Stop: + if eventType == QtCore.QEvent.MouseButtonRelease: + data.state = FlickData.Steady + + # If the user had a very limited scroll smaller than the + # threshold consider it a regular press and release. + if data.travelled < self.travel_threshold: + return self._propagate_click(obj, event, data) + + return True + elif eventType == QtCore.QEvent.MouseMove: + # Reset the press position and offset to allow us to "continue" + # the scroll from the new point the user clicked and then held + # down to continue scrolling after AutoScroll. + self._set_press_pos_and_offset(event, data) + data.state = FlickData.ManualScroll + + data.dragPos = QtGui.QCursor.pos() + if not self.ticker.isActive(): + self.ticker.start(self.tick_time, self) + return True + + return False + + def _set_press_pos_and_offset(self, event, data): + """Store current event position on Press""" + data.state = FlickData.Pressed + data.pressPos = copy.copy(event.pos()) + data.offset = scrollOffset(data.widget) + data.travelled = 0 + + def _propagate_click(self, obj, event, data): + """Propagate from Pressed state with MouseButtonRelease event. + + Use only on button release in certain states to propagate a click, + for example when the user dragged only a slight distance under the + travel threshold. + + """ + + data.state = FlickData.Pressed + data.pressPos = copy.copy(event.pos()) + data.offset = scrollOffset(data.widget) + data.travelled = 0 + self.eventFilter(obj, event) + return True + + def timerEvent(self, event): + + count = 0 + for data in self.flickData.values(): + if data.state == FlickData.ManualScroll: + count += 1 + cursorPos = QtGui.QCursor.pos() + data.speed = cursorPos - data.dragPos + data.dragPos = cursorPos + elif data.state == FlickData.AutoScroll: + count += 1 + data.speed = deaccelerate(data.speed, + a=self.drag, + maxVal=self.max_speed) + p = scrollOffset(data.widget) + new_p = p - data.speed + setScrollOffset(data.widget, new_p) + + if scrollOffset(data.widget) == p: + # If this scroll resulted in no change on the widget + # we reached the end of the list and set the speed to + # zero. + data.speed = QtCore.QPoint(0, 0) + + if data.speed == QtCore.QPoint(0, 0): + data.state = FlickData.Steady + + if count == 0: + self.ticker.stop() + + super(FlickCharm, self).timerEvent(event) + + +def scrollOffset(widget): + x = widget.horizontalScrollBar().value() + y = widget.verticalScrollBar().value() + return QtCore.QPoint(x, y) + + +def setScrollOffset(widget, p): + widget.horizontalScrollBar().setValue(p.x()) + widget.verticalScrollBar().setValue(p.y()) + + +def deaccelerate(speed, a=1, maxVal=64): + + x = max(min(speed.x(), maxVal), -maxVal) + y = max(min(speed.y(), maxVal), -maxVal) + if x > 0: + x = max(0, x - a) + elif x < 0: + x = min(0, x + a) + if y > 0: + y = max(0, y - a) + elif y < 0: + y = min(0, y + a) + return QtCore.QPoint(x, y) + + +def removeAll(list, val): + found = False + ret = [] + for element in list: + if element == val: + found = True + else: + ret.append(element) + return found, ret diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py new file mode 100644 index 0000000000..8cd117074c --- /dev/null +++ b/pype/tools/launcher/lib.py @@ -0,0 +1,67 @@ +"""Utility script for updating database with configuration files + +Until assets are created entirely in the database, this script +provides a bridge between the file-based project inventory and configuration. + +- Migrating an old project: + $ python -m avalon.inventory --extract --silo-parent=f02_prod + $ python -m avalon.inventory --upload + +- Managing an existing project: + 1. Run `python -m avalon.inventory --load` + 2. Update the .inventory.toml or .config.toml + 3. Run `python -m avalon.inventory --save` + +""" + +from avalon import io, lib, pipeline + + +def list_project_tasks(): + """List the project task types available in the current project""" + project = io.find_one({"type": "project"}) + return [task["name"] for task in project["config"]["tasks"]] + + +def get_application_actions(project): + """Define dynamic Application classes for project using `.toml` files + + Args: + project (dict): project document from the database + + Returns: + list: list of dictionaries + """ + + apps = [] + for app in project["config"]["apps"]: + try: + app_name = app["name"] + app_definition = lib.get_application(app_name) + except Exception as exc: + print("Unable to load application: %s - %s" % (app['name'], exc)) + continue + + # Get from app definition, if not there from app in project + icon = app_definition.get("icon", app.get("icon", "folder-o")) + color = app_definition.get("color", app.get("color", None)) + order = app_definition.get("order", app.get("order", 0)) + label = app.get("label") or app_definition.get("label") or app["name"] + group = app.get("group") or app_definition.get("group") + + action = type( + "app_{}".format(app_name), + (pipeline.Application,), + { + "name": app_name, + "label": label, + "group": group, + "icon": icon, + "color": color, + "order": order, + "config": app_definition.copy() + } + ) + + apps.append(action) + return apps diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py new file mode 100644 index 0000000000..17c28c19b3 --- /dev/null +++ b/pype/tools/launcher/models.py @@ -0,0 +1,292 @@ +import os +import copy +import logging +import collections + +from . import lib +from Qt import QtCore, QtGui +from avalon.vendor import qtawesome +from avalon import io, style, api + + +log = logging.getLogger(__name__) + +icons_dir = "C:/Users/iLLiCiT/Desktop/Prace/pype-setup/repos/pype/pype/resources/app_icons" + + +class TaskModel(QtGui.QStandardItemModel): + """A model listing the tasks combined for a list of assets""" + + def __init__(self, parent=None): + super(TaskModel, self).__init__(parent=parent) + self._num_assets = 0 + + self.default_icon = qtawesome.icon( + "fa.male", color=style.colors.default + ) + self.no_task_icon = qtawesome.icon( + "fa.exclamation-circle", color=style.colors.mid + ) + + self._icons = {} + + self._get_task_icons() + + def _get_task_icons(self): + if io.Session.get("AVALON_PROJECT") is None: + return + + # Get the project configured icons from database + project = io.find_one({"type": "project"}) + for task in project["config"].get("tasks") or []: + icon_name = task.get("icon") + if icon_name: + self._icons[task["name"]] = qtawesome.icon( + "fa.{}".format(icon_name), color=style.colors.default + ) + + def set_assets(self, asset_ids=None, asset_docs=None): + """Set assets to track by their database id + + Arguments: + asset_ids (list): List of asset ids. + asset_docs (list): List of asset entities from MongoDB. + + """ + + if asset_docs is None and asset_ids is not None: + # find assets in db by query + asset_docs = list(io.find({ + "type": "asset", + "_id": {"$in": asset_ids} + })) + db_assets_ids = tuple(asset_doc["_id"] for asset_doc in asset_docs) + + # check if all assets were found + not_found = tuple( + str(asset_id) + for asset_id in asset_ids + if asset_id not in db_assets_ids + ) + + assert not not_found, "Assets not found by id: {0}".format( + ", ".join(not_found) + ) + + self.clear() + + if not asset_docs: + return + + task_names = collections.Counter() + for asset_doc in asset_docs: + asset_tasks = asset_doc.get("data", {}).get("tasks", []) + task_names.update(asset_tasks) + + self.beginResetModel() + + if not task_names: + item = QtGui.QStandardItem(self.no_task_icon, "No task") + item.setEnabled(False) + self.appendRow(item) + + else: + for task_name, count in sorted(task_names.items()): + icon = self._icons.get(task_name, self.default_icon) + item = QtGui.QStandardItem(icon, task_name) + self.appendRow(item) + + self.endResetModel() + + def headerData(self, section, orientation, role): + if ( + role == QtCore.Qt.DisplayRole + and orientation == QtCore.Qt.Horizontal + and section == 0 + ): + return "Tasks" + return super(TaskModel, self).headerData(section, orientation, role) + + +class ActionModel(QtGui.QStandardItemModel): + ACTION_ROLE = QtCore.Qt.UserRole + GROUP_ROLE = QtCore.Qt.UserRole + 1 + + def __init__(self, parent=None): + super(ActionModel, self).__init__(parent=parent) + self._icon_cache = {} + self._group_icon_cache = {} + self._session = {} + self._groups = {} + self.default_icon = qtawesome.icon("fa.cube", color="white") + # Cache of available actions + self._registered_actions = list() + + self.discover() + + def discover(self): + """Set up Actions cache. Run this for each new project.""" + if io.Session.get("AVALON_PROJECT") is None: + self._registered_actions = list() + return + + # Discover all registered actions + actions = api.discover(api.Action) + + # Get available project actions and the application actions + project_doc = io.find_one({"type": "project"}) + app_actions = lib.get_application_actions(project_doc) + actions.extend(app_actions) + + self._registered_actions = actions + + def get_icon(self, action, skip_default=False): + icon_name = action.icon + if not icon_name: + if skip_default: + return None + return self.default_icon + + icon = self._icon_cache.get(icon_name) + if icon: + return icon + + icon = self.default_icon + icon_path = os.path.join(icons_dir, icon_name) + if os.path.exists(icon_path): + icon = QtGui.QIcon(icon_path) + self._icon_cache[icon_name] = icon + return icon + + try: + icon_color = getattr(action, "color", None) or "white" + icon = qtawesome.icon( + "fa.{}".format(icon_name), color=icon_color + ) + + except Exception: + print("Can't load icon \"{}\"".format(icon_name)) + + self._icon_cache[icon_name] = self.default_icon + return icon + + def refresh(self): + # Validate actions based on compatibility + self.clear() + + self._groups.clear() + + actions = self.filter_compatible_actions(self._registered_actions) + + self.beginResetModel() + + single_actions = [] + grouped_actions = collections.defaultdict(list) + for action in actions: + group_name = getattr(action, "group", None) + if not group_name: + single_actions.append(action) + else: + grouped_actions[group_name].append(action) + + for group_name, actions in tuple(grouped_actions.items()): + if len(actions) == 1: + grouped_actions.pop(group_name) + single_actions.append(actions[0]) + + items_by_order = collections.defaultdict(list) + for action in single_actions: + icon = self.get_icon(action) + item = QtGui.QStandardItem( + icon, str(action.label or action.name) + ) + item.setData(action, self.ACTION_ROLE) + items_by_order[action.order].append(item) + + for group_name, actions in grouped_actions.items(): + icon = None + order = None + for action in actions: + if order is None or action.order < order: + order = action.order + + if icon is None: + _icon = self.get_icon(action) + if _icon: + icon = _icon + + if icon is None: + icon = self.default_icon + + item = QtGui.QStandardItem(icon, group_name) + item.setData(actions, self.ACTION_ROLE) + item.setData(True, self.GROUP_ROLE) + + items_by_order[order].append(item) + + for order in sorted(items_by_order.keys()): + for item in items_by_order[order]: + self.appendRow(item) + + self.endResetModel() + + def set_session(self, session): + assert isinstance(session, dict) + self._session = copy.deepcopy(session) + self.refresh() + + def filter_compatible_actions(self, actions): + """Collect all actions which are compatible with the environment + + Each compatible action will be translated to a dictionary to ensure + the action can be visualized in the launcher. + + Args: + actions (list): list of classes + + Returns: + list: collection of dictionaries sorted on order int he + """ + + compatible = [] + for action in actions: + if action().is_compatible(self._session): + compatible.append(action) + + # Sort by order and name + return sorted( + compatible, + key=lambda action: (action.order, action.name) + ) + + +class ProjectModel(QtGui.QStandardItemModel): + """List of projects""" + + def __init__(self, parent=None): + super(ProjectModel, self).__init__(parent=parent) + + self.hide_invisible = False + self.project_icon = qtawesome.icon("fa.map", color="white") + + def refresh(self): + self.clear() + self.beginResetModel() + + for project_doc in self.get_projects(): + item = QtGui.QStandardItem(self.project_icon, project_doc["name"]) + self.appendRow(item) + + self.endResetModel() + + def get_projects(self): + project_docs = [] + for project_doc in sorted(io.projects(), key=lambda x: x["name"]): + if ( + self.hide_invisible + and not project_doc["data"].get("visible", True) + ): + continue + project_docs.append(project_doc) + + return project_docs diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py new file mode 100644 index 0000000000..c48376ae91 --- /dev/null +++ b/pype/tools/launcher/widgets.py @@ -0,0 +1,419 @@ +import copy +from Qt import QtWidgets, QtCore, QtGui +from avalon.vendor import qtawesome +from avalon import api + +from .models import TaskModel, ActionModel, ProjectModel +from .flickcharm import FlickCharm + + +class ProjectBar(QtWidgets.QWidget): + project_changed = QtCore.Signal(int) + + def __init__(self, parent=None): + super(ProjectBar, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + + self.model = ProjectModel() + self.model.hide_invisible = True + + self.view = QtWidgets.QComboBox() + self.view.setModel(self.model) + self.view.setRootModelIndex(QtCore.QModelIndex()) + + layout.addWidget(self.view) + + self.setSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.Maximum + ) + + # Initialize + self.refresh() + + # Signals + self.view.currentIndexChanged.connect(self.project_changed) + + # Set current project by default if it's set. + project_name = api.Session.get("AVALON_PROJECT") + if project_name: + self.set_project(project_name) + + def get_current_project(self): + return self.view.currentText() + + def set_project(self, project_name): + index = self.view.findText(project_name) + if index >= 0: + self.view.setCurrentIndex(index) + + def refresh(self): + prev_project_name = self.get_current_project() + + # Refresh without signals + self.view.blockSignals(True) + self.model.refresh() + + self.set_project(prev_project_name) + + self.view.blockSignals(False) + + self.project_changed.emit(self.view.currentIndex()) + + +class ActionDelegate(QtWidgets.QStyledItemDelegate): + extender_lines = 2 + extender_bg_brush = QtGui.QBrush(QtGui.QColor(100, 100, 100))#, 160)) + extender_fg = QtGui.QColor(255, 255, 255)#, 160) + + def __init__(self, group_role, *args, **kwargs): + super(ActionDelegate, self).__init__(*args, **kwargs) + self.group_role = group_role + + def paint(self, painter, option, index): + super(ActionDelegate, self).paint(painter, option, index) + is_group = index.data(self.group_role) + if not is_group: + return + + extender_width = int(option.decorationSize.width() / 2) + extender_height = int(option.decorationSize.height() / 2) + + exteder_rect = QtCore.QRectF( + option.rect.x() + (option.rect.width() / 10), + option.rect.y() + (option.rect.height() / 10), + extender_width, + extender_height + ) + path = QtGui.QPainterPath() + path.addRoundedRect(exteder_rect, 2, 2) + + painter.fillPath(path, self.extender_bg_brush) + + painter.setPen(self.extender_fg) + painter.drawPath(path) + + divider = (2 * self.extender_lines) + 1 + line_height = extender_height / divider + line_width = extender_width - (extender_width / 5) + pos_x = exteder_rect.x() + extender_width / 10 + pos_y = exteder_rect.y() + line_height + for _ in range(self.extender_lines): + line_rect = QtCore.QRectF( + pos_x, pos_y, line_width, round(line_height) + ) + painter.fillRect(line_rect, self.extender_fg) + pos_y += 2 * line_height + + +class ActionBar(QtWidgets.QWidget): + """Launcher interface""" + + action_clicked = QtCore.Signal(object) + + def __init__(self, parent=None): + super(ActionBar, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(8, 0, 8, 0) + + view = QtWidgets.QListView(self) + view.setObjectName("ActionView") + view.setViewMode(QtWidgets.QListView.IconMode) + view.setResizeMode(QtWidgets.QListView.Adjust) + view.setSelectionMode(QtWidgets.QListView.NoSelection) + view.setWrapping(True) + view.setGridSize(QtCore.QSize(70, 75)) + view.setIconSize(QtCore.QSize(30, 30)) + view.setSpacing(0) + view.setWordWrap(True) + + model = ActionModel(self) + view.setModel(model) + + delegate = ActionDelegate(model.GROUP_ROLE, self) + view.setItemDelegate(delegate) + + layout.addWidget(view) + + self.model = model + self.view = view + + # Make view flickable + flick = FlickCharm(parent=view) + flick.activateOn(view) + + self.set_row_height(1) + + view.clicked.connect(self.on_clicked) + + def set_row_height(self, rows): + self.setMinimumHeight(rows * 75) + + def on_clicked(self, index): + if index.isValid(): + is_group = action = index.data(self.model.GROUP_ROLE) + if not is_group: + action = index.data(self.model.ACTION_ROLE) + self.action_clicked.emit(action) + return + + menu = QtWidgets.QMenu(self) + actions = index.data(self.model.ACTION_ROLE) + actions_mapping = {} + for action in actions: + menu_action = QtWidgets.QAction(action.label or action.name) + menu.addAction(menu_action) + actions_mapping[menu_action] = action + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + action = actions_mapping[result] + self.action_clicked.emit(action) + + +class TasksWidget(QtWidgets.QWidget): + """Widget showing active Tasks""" + + task_changed = QtCore.Signal() + selection_mode = ( + QtCore.QItemSelectionModel.Select | QtCore.QItemSelectionModel.Rows + ) + + def __init__(self): + super(TasksWidget, self).__init__() + + view = QtWidgets.QTreeView() + view.setIndentation(0) + model = TaskModel() + view.setModel(model) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(view) + + view.selectionModel().selectionChanged.connect(self.task_changed) + + self.model = model + self.view = view + + self._last_selected_task = None + + def set_asset(self, asset_id): + if asset_id is None: + # Asset deselected + self.model.set_assets() + return + + # Try and preserve the last selected task and reselect it + # after switching assets. If there's no currently selected + # asset keep whatever the "last selected" was prior to it. + current = self.get_current_task() + if current: + self._last_selected_task = current + + self.model.set_assets([asset_id]) + + if self._last_selected_task: + self.select_task(self._last_selected_task) + + # Force a task changed emit. + self.task_changed.emit() + + def select_task(self, task_name): + """Select a task by name. + + If the task does not exist in the current model then selection is only + cleared. + + Args: + task (str): Name of the task to select. + + """ + + # Clear selection + self.view.selectionModel().clearSelection() + + # Select the task + for row in range(self.model.rowCount()): + index = self.model.index(row, 0) + _task_name = index.data(QtCore.Qt.DisplayRole) + if _task_name == task_name: + self.view.selectionModel().select(index, self.selection_mode) + # Set the currently active index + self.view.setCurrentIndex(index) + break + + def get_current_task(self): + """Return name of task at current index (selected) + + Returns: + str: Name of the current task. + + """ + index = self.view.currentIndex() + if self.view.selectionModel().isSelected(index): + return index.data(QtCore.Qt.DisplayRole) + + +class ActionHistory(QtWidgets.QPushButton): + trigger_history = QtCore.Signal(tuple) + + def __init__(self, parent=None): + super(ActionHistory, self).__init__(parent=parent) + + self.max_history = 15 + + self.setFixedWidth(25) + self.setFixedHeight(25) + + self.setIcon(qtawesome.icon("fa.history", color="#CCCCCC")) + self.setIconSize(QtCore.QSize(15, 15)) + + self._history = [] + self.clicked.connect(self.show_history) + + def show_history(self): + # Show history popup + if not self._history: + return + + point = QtGui.QCursor().pos() + + widget = QtWidgets.QListWidget() + widget.setSelectionMode(widget.NoSelection) + + widget.setStyleSheet(""" + * { + font-family: "Courier New"; + } + """) + + largest_label_num_chars = 0 + largest_action_label = max(len(x[0].label) for x in self._history) + action_session_role = QtCore.Qt.UserRole + 1 + + for action, session in reversed(self._history): + project = session.get("AVALON_PROJECT") + asset = session.get("AVALON_ASSET") + task = session.get("AVALON_TASK") + breadcrumb = " > ".join(x for x in [project, asset, task] if x) + + m = "{{action:{0}}} | {{breadcrumb}}".format(largest_action_label) + label = m.format(action=action.label, breadcrumb=breadcrumb) + + icon_name = action.icon + color = action.color or "white" + icon = qtawesome.icon("fa.%s" % icon_name, color=color) + item = QtWidgets.QListWidgetItem(icon, label) + item.setData(action_session_role, (action, session)) + + largest_label_num_chars = max(largest_label_num_chars, len(label)) + + widget.addItem(item) + + # Show history + width = 40 + (largest_label_num_chars * 7) # padding + icon + text + entry_height = 21 + height = entry_height * len(self._history) + + dialog = QtWidgets.QDialog(parent=self) + dialog.setWindowTitle("Action History") + dialog.setWindowFlags(QtCore.Qt.FramelessWindowHint | + QtCore.Qt.Popup) + dialog.setSizePolicy(QtWidgets.QSizePolicy.Ignored, + QtWidgets.QSizePolicy.Ignored) + + layout = QtWidgets.QVBoxLayout(dialog) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(widget) + + def on_clicked(index): + data = index.data(action_session_role) + self.trigger_history.emit(data) + dialog.close() + + widget.clicked.connect(on_clicked) + + dialog.setGeometry(point.x() - width, + point.y() - height, + width, + height) + dialog.exec_() + + self.widget_popup = widget + + def add_action(self, action, session): + key = (action, copy.deepcopy(session)) + + # Remove entry if already exists + try: + index = self._history.index(key) + self._history.pop(index) + except ValueError: + pass + + self._history.append(key) + + # Slice the end of the list if we exceed the max history + if len(self._history) > self.max_history: + self._history = self._history[-self.max_history:] + + def clear_history(self): + self._history[:] = [] + + +class SlidePageWidget(QtWidgets.QStackedWidget): + """Stacked widget that nicely slides between its pages""" + + directions = { + "left": QtCore.QPoint(-1, 0), + "right": QtCore.QPoint(1, 0), + "up": QtCore.QPoint(0, 1), + "down": QtCore.QPoint(0, -1) + } + + def slide_view(self, index, direction="right"): + + if self.currentIndex() == index: + return + + offset = self.directions.get(direction) + assert offset is not None, "invalid slide direction: %s" % (direction,) + + width = self.frameRect().width() + height = self.frameRect().height() + offset = QtCore.QPoint(offset.x() * width, offset.y() * height) + + new_page = self.widget(index) + new_page.setGeometry(0, 0, width, height) + curr_pos = new_page.pos() + new_page.move(curr_pos + offset) + new_page.show() + new_page.raise_() + + current_page = self.currentWidget() + + b_pos = QtCore.QByteArray(b"pos") + + anim_old = QtCore.QPropertyAnimation(current_page, b_pos, self) + anim_old.setDuration(250) + anim_old.setStartValue(curr_pos) + anim_old.setEndValue(curr_pos - offset) + anim_old.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim_new = QtCore.QPropertyAnimation(new_page, b_pos, self) + anim_new.setDuration(250) + anim_new.setStartValue(curr_pos + offset) + anim_new.setEndValue(curr_pos) + anim_new.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim_group = QtCore.QParallelAnimationGroup(self) + anim_group.addAnimation(anim_old) + anim_group.addAnimation(anim_new) + + def slide_finished(): + self.setCurrentWidget(new_page) + + anim_group.finished.connect(slide_finished) + anim_group.start() From 1a13997eb9c3edcb495b5c4149b23dec54eeaf93 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 21:53:13 +0200 Subject: [PATCH 282/947] use resources --- pype/tools/launcher/models.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index 17c28c19b3..ae29c65297 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -7,12 +7,10 @@ from . import lib from Qt import QtCore, QtGui from avalon.vendor import qtawesome from avalon import io, style, api - +from pype.api import resources log = logging.getLogger(__name__) -icons_dir = "C:/Users/iLLiCiT/Desktop/Prace/pype-setup/repos/pype/pype/resources/app_icons" - class TaskModel(QtGui.QStandardItemModel): """A model listing the tasks combined for a list of assets""" @@ -152,7 +150,7 @@ class ActionModel(QtGui.QStandardItemModel): return icon icon = self.default_icon - icon_path = os.path.join(icons_dir, icon_name) + icon_path = resources.get_resource(icon_name) if os.path.exists(icon_path): icon = QtGui.QIcon(icon_path) self._icon_cache[icon_name] = icon From 80ce7dc738d3af53c5002ba0f8cc650fb2e412a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 22:02:38 +0200 Subject: [PATCH 283/947] replaced `<` with icon --- pype/tools/launcher/app.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index 8bce705fc9..9cef313bf5 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -138,8 +138,11 @@ class AssetsPanel(QtWidgets.QWidget): project_bar = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout(project_bar) layout.setSpacing(4) - back = QtWidgets.QPushButton("<") - back.setFixedWidth(25) + + icon = qtawesome.icon("fa.angle-left", color="white") + back = QtWidgets.QPushButton() + back.setIcon(icon) + back.setFixedWidth(23) back.setFixedHeight(23) projects = ProjectBar() projects.layout().setContentsMargins(0, 0, 0, 0) @@ -147,9 +150,9 @@ class AssetsPanel(QtWidgets.QWidget): layout.addWidget(projects) # assets - _assets_widgets = QtWidgets.QWidget() - _assets_widgets.setContentsMargins(0, 0, 0, 0) - assets_layout = QtWidgets.QVBoxLayout(_assets_widgets) + assets_proxy_widgets = QtWidgets.QWidget() + assets_proxy_widgets.setContentsMargins(0, 0, 0, 0) + assets_layout = QtWidgets.QVBoxLayout(assets_proxy_widgets) assets_widgets = AssetWidget() # Make assets view flickable @@ -167,7 +170,7 @@ class AssetsPanel(QtWidgets.QWidget): body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) body.setOrientation(QtCore.Qt.Horizontal) - body.addWidget(_assets_widgets) + body.addWidget(assets_proxy_widgets) body.addWidget(tasks_widgets) body.setStretchFactor(0, 100) body.setStretchFactor(1, 65) From 0c3076c931eab668a486226f4dd23a45d8eef5ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 11:30:35 +0200 Subject: [PATCH 284/947] it is not checked blocked state on parents tasks but if status name is omitted --- pype/modules/ftrack/events/event_next_task_update.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index 2df3800d8a..1f8407e559 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -148,11 +148,12 @@ class NextTaskUpdate(BaseEvent): continue parents_task_status = statuses_by_id[parents_task["status_id"]] - low_state_name = parents_task_status["state"]["name"].lower() - # Skip if task's status is in blocked state (e.g. Omitted) - if low_state_name != "blocked": + low_status_name = parents_task_status["name"].lower() + # Skip if task's status name "Omitted" + if low_status_name == "omitted": continue + low_state_name = parents_task_status["state"]["name"].lower() if low_state_name != "done": all_same_type_taks_done = False break From c214e7516c119e75f654e16da30183a7d5a11e65 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 11 Aug 2020 13:47:50 +0200 Subject: [PATCH 285/947] feat(ps): adding psd batch workflow to standalone publisher --- .../publish/collect_context.py | 129 ++++++++++-------- .../publish/collect_hierarchy.py | 1 + .../publish/collect_matching_asset.py | 26 ++++ .../publish/collect_project_assets.py | 21 +++ .../publish/collect_psd_instances.py | 71 ++++++++++ 5 files changed, 190 insertions(+), 58 deletions(-) create mode 100644 pype/plugins/standalonepublisher/publish/collect_matching_asset.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_project_assets.py create mode 100644 pype/plugins/standalonepublisher/publish/collect_psd_instances.py diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 4dcb25f927..370e00fd7f 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -17,10 +17,8 @@ import os import pyblish.api from avalon import io import json -import logging import clique - -log = logging.getLogger("collector") +from pprint import pformat class CollectContextDataSAPublish(pyblish.api.ContextPlugin): @@ -33,6 +31,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.49 hosts = ["standalonepublisher"] + # presets + batch_extensions = ["edl", "xml", "psd"] + def process(self, context): # get json paths from os and load them io.install() @@ -52,73 +53,80 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): with open(input_json_path, "r") as f: in_data = json.load(f) - self.log.debug(f"_ in_data: {in_data}") + self.log.debug(f"_ in_data: {pformat(in_data)}") self.asset_name = in_data["asset"] self.family = in_data["family"] + self.families = ["ftrack"] + self.family_preset_key = in_data["family_preset_key"] asset = io.find_one({"type": "asset", "name": self.asset_name}) context.data["asset"] = asset # exception for editorial - if "editorial" in self.family: - # avoid subset name duplicity - if not context.data.get("subsetNamesCheck"): - context.data["subsetNamesCheck"] = list() - - in_data_list = list() - representations = in_data.pop("representations") - for repr in representations: - in_data_copy = in_data.copy() - ext = repr["ext"][1:] - subset = in_data_copy["subset"] - # filter out non editorial files - if ext not in ["edl", "xml"]: - in_data_copy["representations"] = [repr] - in_data_copy["subset"] = f"{ext}{subset}" - in_data_list.append(in_data_copy) - - files = repr.pop("files") - - # delete unneeded keys - delete_repr_keys = ["frameStart", "frameEnd"] - for k in delete_repr_keys: - if repr.get(k): - repr.pop(k) - - # convert files to list if it isnt - if not isinstance(files, list): - files = [files] - - self.log.debug(f"_ files: {files}") - for index, f in enumerate(files): - index += 1 - # copy dictionaries - in_data_copy = in_data_copy.copy() - repr_new = repr.copy() - - repr_new["files"] = f - repr_new["name"] = ext - in_data_copy["representations"] = [repr_new] - - # create subset Name - new_subset = f"{ext}{index}{subset}" - while new_subset in context.data["subsetNamesCheck"]: - index += 1 - new_subset = f"{ext}{index}{subset}" - - context.data["subsetNamesCheck"].append(new_subset) - in_data_copy["subset"] = new_subset - in_data_list.append(in_data_copy) - self.log.info(f"Creating subset: {ext}{index}{subset}") + if self.family_preset_key in ["editorial", "psd_batch"]: + in_data_list = self.multiple_instances(context, in_data) else: in_data_list = [in_data] - self.log.debug(f"_ in_data_list: {in_data_list}") + self.log.debug(f"_ in_data_list: {pformat(in_data_list)}") for in_data in in_data_list: # create instance self.create_instance(context, in_data) + def multiple_instances(self, context, in_data): + # avoid subset name duplicity + if not context.data.get("subsetNamesCheck"): + context.data["subsetNamesCheck"] = list() + + in_data_list = list() + representations = in_data.pop("representations") + for repr in representations: + in_data_copy = in_data.copy() + ext = repr["ext"][1:] + subset = in_data_copy["subset"] + # filter out non editorial files + if ext not in self.batch_extensions: + in_data_copy["representations"] = [repr] + in_data_copy["subset"] = f"{ext}{subset}" + in_data_list.append(in_data_copy) + + files = repr.get("files") + + # delete unneeded keys + delete_repr_keys = ["frameStart", "frameEnd"] + for k in delete_repr_keys: + if repr.get(k): + repr.pop(k) + + # convert files to list if it isnt + if not isinstance(files, list): + files = [files] + + self.log.debug(f"_ files: {files}") + for index, f in enumerate(files): + index += 1 + # copy dictionaries + in_data_copy = in_data_copy.copy() + repr_new = repr.copy() + + repr_new["files"] = f + repr_new["name"] = ext + in_data_copy["representations"] = [repr_new] + + # create subset Name + new_subset = f"{ext}{index}{subset}" + while new_subset in context.data["subsetNamesCheck"]: + index += 1 + new_subset = f"{ext}{index}{subset}" + + context.data["subsetNamesCheck"].append(new_subset) + in_data_copy["subset"] = new_subset + in_data_list.append(in_data_copy) + self.log.info(f"Creating subset: {ext}{index}{subset}") + + return in_data_list + def create_instance(self, context, in_data): subset = in_data["subset"] @@ -138,11 +146,11 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "frameEnd": in_data.get("representations", [None])[0].get( "frameEnd", None ), - "families": [self.family, "ftrack"], + "families": self.families + [self.family_preset_key], } ) - self.log.info("collected instance: {}".format(instance.data)) - self.log.info("parsing data: {}".format(in_data)) + self.log.info("collected instance: {}".format(pformat(instance.data))) + self.log.info("parsing data: {}".format(pformat(in_data))) instance.data["destination_list"] = list() instance.data["representations"] = list() @@ -165,4 +173,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): component["tags"] = ["review"] self.log.debug("Adding review family") + if "psd" in component["name"]: + instance.data["source"] = component["files"] + component["thumbnail"] = True + self.log.debug("Adding image:psd_batch family") + instance.data["representations"].append(component) diff --git a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py index b5d37d0a6c..ac7413706a 100644 --- a/pype/plugins/standalonepublisher/publish/collect_hierarchy.py +++ b/pype/plugins/standalonepublisher/publish/collect_hierarchy.py @@ -162,6 +162,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): label = "Collect Hierarchy Context" order = pyblish.api.CollectorOrder + 0.102 hosts = ["standalonepublisher"] + families = ["shot"] def update_dict(self, ex_dict, new_dict): for key in ex_dict: diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py new file mode 100644 index 0000000000..3575f581df --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -0,0 +1,26 @@ +import os +import pyblish.api +from pprint import pformat + + +class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): + """ + Collecting temp json data sent from a host context + and path for returning json data back to hostself. + """ + + label = "Collect Matching Asset to Instance" + order = pyblish.api.CollectorOrder - 0.05 + hosts = ["standalonepublisher"] + family = ["image"] + + def process(self, instance): + project_assets = instance.context.data["projectAssets"] + source_file = os.path.basename(instance.data["source"]) + asset = next((project_assets[name] for name in project_assets + if name in source_file), None) + + if asset: + instance.data["asset"] = asset["name"] + instance.data["assetEntity"] = asset + self.log.info(f"Matching asset assigned: {pformat(asset)}") diff --git a/pype/plugins/standalonepublisher/publish/collect_project_assets.py b/pype/plugins/standalonepublisher/publish/collect_project_assets.py new file mode 100644 index 0000000000..1ca4144c81 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_project_assets.py @@ -0,0 +1,21 @@ +import pyblish.api +from avalon import io +from pprint import pformat + + +class CollectProjectAssets(pyblish.api.ContextPlugin): + """ + Collect all available project assets to context data. + """ + + label = "Collect Project Assets" + order = pyblish.api.CollectorOrder - 0.1 + hosts = ["standalonepublisher"] + + def process(self, context): + project_assets = { + asset_doc["name"]: asset_doc + for asset_doc in io.find({"type": "asset"}) + } + context.data["projectAssets"] = project_assets + self.log.debug(f"_ project_assets: {pformat(project_assets)}") diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py new file mode 100644 index 0000000000..1806439969 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -0,0 +1,71 @@ +import pyblish.api +from pprint import pformat + + +class CollectPsdInstances(pyblish.api.InstancePlugin): + """ + Collect all available instances from psd batch. + """ + + label = "Collect Psd Instances" + order = pyblish.api.CollectorOrder + 0.492 + hosts = ["standalonepublisher"] + families = ["psd_batch"] + + # presets + subsets = { + "imageForLayout": { + "task": "background", + "family": "imageForLayout" + }, + "imageForComp": { + "task": "background", + "family": "imageForComp" + }, + "workfileBackground": { + "task": "background", + "family": "workfile" + }, + } + + def process(self, instance): + context = instance.context + asset_data = instance.data["assetEntity"] + asset = instance.data["asset"] + anatomy_data = instance.data["anatomyData"].copy() + + for subset_name, subset_data in self.subsets.items(): + instance_name = f"{asset}_{subset_name}" + task = subset_data.get("task", "background") + + # create new instance + new_instance = context.create_instance(instance_name) + # add original instance data except name key + new_instance.data.update({k: v for k, v in instance.data.items() + if k not in "name"}) + # add subset data from preset + new_instance.data.update(subset_data) + + label = f"{instance_name}" + new_instance.data["label"] = label + new_instance.data["subset"] = subset_name + new_instance.data["families"].append("image") + + # fix anatomy data + anatomy_data_new = anatomy_data.copy() + # updating hierarchy data + anatomy_data_new.update({ + "asset": asset_data["name"], + "task": task, + "subset": subset_name + }) + new_instance.data["anatomyData"] = anatomy_data_new + + inst_data = new_instance.data + self.log.debug( + f"_ inst_data: {pformat(inst_data)}") + + self.log.info(f"Created new instance: {instance_name}") + + # delete original instance + context.remove(instance) From a6203f06daa9471897bc5382c531a8c3831f4aee Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 11 Aug 2020 12:56:36 +0100 Subject: [PATCH 286/947] Multiple reviews where being overwritten to one. --- pype/plugins/maya/publish/collect_review.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 063a854bd1..0575d90452 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -69,7 +69,12 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data['remove'] = True i += 1 else: - instance.data['subset'] = task + 'Review' + subset = "{}{}{}".format( + task, + instance.data["subset"][0].upper(), + instance.data["subset"][1:] + ) + instance.data['subset'] = subset instance.data['review_camera'] = camera instance.data['frameStartFtrack'] = instance.data["frameStartHandle"] instance.data['frameEndFtrack'] = instance.data["frameEndHandle"] From 0b6ef8c6f51b8a427a2e782547bc7476474cdd9e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 11 Aug 2020 13:52:21 +0100 Subject: [PATCH 287/947] Isolate view on instance members if more than camera (one object). --- pype/plugins/maya/publish/extract_playblast.py | 5 +++++ pype/plugins/maya/publish/extract_thumbnail.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/pype/plugins/maya/publish/extract_playblast.py b/pype/plugins/maya/publish/extract_playblast.py index 8d45f98b90..3c9811d4c4 100644 --- a/pype/plugins/maya/publish/extract_playblast.py +++ b/pype/plugins/maya/publish/extract_playblast.py @@ -77,6 +77,11 @@ class ExtractPlayblast(pype.api.Extractor): pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) + # Isolate view is requested by having objects in the set besides a + # camera. + if len(instance.data["setMembers"]) > 1: + preset["isolate"] = instance.data["setMembers"] + with maintained_time(): filename = preset.get("filename", "%TEMP%") diff --git a/pype/plugins/maya/publish/extract_thumbnail.py b/pype/plugins/maya/publish/extract_thumbnail.py index c0eb2a608e..2edd19a559 100644 --- a/pype/plugins/maya/publish/extract_thumbnail.py +++ b/pype/plugins/maya/publish/extract_thumbnail.py @@ -77,6 +77,11 @@ class ExtractThumbnail(pype.api.Extractor): pm.currentTime(refreshFrameInt - 1, edit=True) pm.currentTime(refreshFrameInt, edit=True) + # Isolate view is requested by having objects in the set besides a + # camera. + if len(instance.data["setMembers"]) > 1: + preset["isolate"] = instance.data["setMembers"] + with maintained_time(): filename = preset.get("filename", "%TEMP%") From 96a779e67ba0c1b74d2856ef0c241257c975c956 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 15:22:18 +0200 Subject: [PATCH 288/947] fix anatomy data copy --- .../standalonepublisher/publish/collect_psd_instances.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index 1806439969..46471a5fdd 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -1,3 +1,4 @@ +import copy import pyblish.api from pprint import pformat @@ -32,7 +33,7 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): context = instance.context asset_data = instance.data["assetEntity"] asset = instance.data["asset"] - anatomy_data = instance.data["anatomyData"].copy() + anatomy_data = instance.data["anatomyData"] for subset_name, subset_data in self.subsets.items(): instance_name = f"{asset}_{subset_name}" @@ -52,7 +53,7 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): new_instance.data["families"].append("image") # fix anatomy data - anatomy_data_new = anatomy_data.copy() + anatomy_data_new = copy.deepcopy(anatomy_data) # updating hierarchy data anatomy_data_new.update({ "asset": asset_data["name"], From 0c6ddf9c99098ccd441fc846c9cdde4a35727451 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 17:58:40 +0200 Subject: [PATCH 289/947] extract layout plugin implementation --- .../publish/extract_layout_images.py | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 pype/plugins/standalonepublisher/publish/extract_layout_images.py diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py new file mode 100644 index 0000000000..c3abf8d36f --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -0,0 +1,102 @@ +import os +import copy +import pype.api +import pyblish.api + +PSDImage = None + + +class ExtractImageForLayout(pype.api.Extractor): + + label = "Extract Images for Layout" + order = pyblish.api.ExtractorOrder + 0.02 + families = ["imageForLayout"] + hosts = ["standalonepublisher"] + + new_instance_family = "image" + + # Presetable + allowed_group_names = ["OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide"] + + 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!" + ) + + 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 non files without .psd extension + if repre["ext"] != ".psd": + continue + + # 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) + + self.create_new_instances(instance, psd_object) + + # Remove the instance from context + instance.context.remove(instance) + + def create_new_instances(self, instance, psd_object): + for layer in psd_object: + if ( + not layer.is_visible() + or layer.name not in self.allowed_group_names + ): + continue + + layer_name = layer.name.replace(" ", "_") + instance_name = f"image_{layer_name}" + new_instance = instance.context.create_instance(instance_name) + for key, value in instance.data.items(): + if key not in ("name", "label", "stagingDir"): + new_instance.data[key] = copy.deepcopy(value) + + new_instance.data["label"] = " ".join( + (new_instance.data["asset"], instance_name) + ) + + new_instance.data["subset"] = instance_name + new_instance.data["anatomyData"]["subset"] = instance_name + + # Set `family` + new_instance.data["family"] = self.new_instance_family + + # 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 + } + new_instance.data["representations"] = [new_repre] + self.log.debug(new_instance.data["name"]) From f38327a25f3e95b84511b095e7e9f9e33f30b2b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 18:10:55 +0200 Subject: [PATCH 290/947] ignore psd from ffprobe data getter --- .../modules/standalonepublish/widgets/widget_drop_frame.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/modules/standalonepublish/widgets/widget_drop_frame.py index 57547a3d5f..785f6adce4 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_frame.py @@ -37,6 +37,7 @@ class DropDataFrame(QtWidgets.QFrame): "image_file": image_extensions, "video_file": video_extensions } + ffprobe_ignore_extensions = [".psd"] def __init__(self, parent): super().__init__() @@ -284,8 +285,10 @@ class DropDataFrame(QtWidgets.QFrame): file_info = data['file_info'] if ( - ext in self.image_extensions - or ext in self.video_extensions + ext not in self.ffprobe_ignore_extensions + and ( + ext in self.image_extensions or ext in self.video_extensions + ) ): probe_data = self.load_data_with_probe(filepath) if 'fps' not in data: From 2de402ad67fc6dd1ddf7e1a2882546c7b28ad0e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 18:12:17 +0200 Subject: [PATCH 291/947] removed unnecessary db queries --- pype/plugins/standalonepublisher/publish/collect_context.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 370e00fd7f..f6d6df72b7 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -45,9 +45,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): presets = config.get_presets() - project = io.find_one({"type": "project"}) - context.data["project"] = project - # get json file context input_json_path = os.environ.get("SAPUBLISH_INPATH") @@ -59,9 +56,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): self.family = in_data["family"] self.families = ["ftrack"] self.family_preset_key = in_data["family_preset_key"] - asset = io.find_one({"type": "asset", "name": self.asset_name}) - context.data["asset"] = asset - # exception for editorial if self.family_preset_key in ["editorial", "psd_batch"]: in_data_list = self.multiple_instances(context, in_data) From c2696e4d4fae61a6022c34f78513b83f87c82383 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 18:24:23 +0200 Subject: [PATCH 292/947] moved CollectProjectAssets to CollectMatchingAssetToInstance as is not used anywhere else yet --- .../publish/collect_matching_asset.py | 34 +++++++++++++++---- .../publish/collect_project_assets.py | 21 ------------ 2 files changed, 27 insertions(+), 28 deletions(-) delete mode 100644 pype/plugins/standalonepublisher/publish/collect_project_assets.py diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index 3575f581df..cd69eeefdc 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -1,5 +1,6 @@ import os import pyblish.api +from avalon import io from pprint import pformat @@ -15,12 +16,31 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): family = ["image"] def process(self, instance): - project_assets = instance.context.data["projectAssets"] source_file = os.path.basename(instance.data["source"]) - asset = next((project_assets[name] for name in project_assets - if name in source_file), None) + self.log.info("Looking for asset document for file \"{}\"".format( + instance.data["source"] + )) + project_assets = { + asset_doc["name"]: asset_doc + for asset_doc in io.find({"type": "asset"}) + } - if asset: - instance.data["asset"] = asset["name"] - instance.data["assetEntity"] = asset - self.log.info(f"Matching asset assigned: {pformat(asset)}") + matching_asset_doc = project_assets.get(source_file) + if matching_asset_doc is None: + for asset_doc in project_assets.values(): + if asset_doc["name"] in source_file: + matching_asset_doc = asset_doc + break + + if not matching_asset_doc: + # TODO better error message + raise AssertionError(( + "Filename does not contain any name of" + " asset documents in database." + )) + + instance.data["asset"] = matching_asset_doc["name"] + instance.data["assetEntity"] = matching_asset_doc + self.log.info( + f"Matching asset found: {pformat(matching_asset_doc)}" + ) diff --git a/pype/plugins/standalonepublisher/publish/collect_project_assets.py b/pype/plugins/standalonepublisher/publish/collect_project_assets.py deleted file mode 100644 index 1ca4144c81..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_project_assets.py +++ /dev/null @@ -1,21 +0,0 @@ -import pyblish.api -from avalon import io -from pprint import pformat - - -class CollectProjectAssets(pyblish.api.ContextPlugin): - """ - Collect all available project assets to context data. - """ - - label = "Collect Project Assets" - order = pyblish.api.CollectorOrder - 0.1 - hosts = ["standalonepublisher"] - - def process(self, context): - project_assets = { - asset_doc["name"]: asset_doc - for asset_doc in io.find({"type": "asset"}) - } - context.data["projectAssets"] = project_assets - self.log.debug(f"_ project_assets: {pformat(project_assets)}") From fa602cd2f5de83b10d11566432b4826cf5647c02 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 18:24:56 +0200 Subject: [PATCH 293/947] skipped presets loading in collect standalone publisher context --- .../plugins/standalonepublisher/publish/collect_context.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index f6d6df72b7..8e610364da 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -38,13 +38,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): # get json paths from os and load them io.install() - # Load presets - presets = context.data.get("presets") - if not presets: - from pype.api import config - - presets = config.get_presets() - # get json file context input_json_path = os.environ.get("SAPUBLISH_INPATH") From bc76f55df2b9a8145b93560a03a85959f5657e8c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 18:41:20 +0200 Subject: [PATCH 294/947] variable cleanup --- .../publish/collect_context.py | 23 ++++++++----------- .../publish/collect_psd_instances.py | 7 +++--- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 8e610364da..f0498cdde8 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -17,6 +17,7 @@ import os import pyblish.api from avalon import io import json +import copy import clique from pprint import pformat @@ -33,6 +34,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): # presets batch_extensions = ["edl", "xml", "psd"] + default_families = ["ftrack"] def process(self, context): # get json paths from os and load them @@ -45,12 +47,8 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): in_data = json.load(f) self.log.debug(f"_ in_data: {pformat(in_data)}") - self.asset_name = in_data["asset"] - self.family = in_data["family"] - self.families = ["ftrack"] - self.family_preset_key = in_data["family_preset_key"] # exception for editorial - if self.family_preset_key in ["editorial", "psd_batch"]: + if in_data["family_preset_key"] in ["editorial", "psd_batch"]: in_data_list = self.multiple_instances(context, in_data) else: in_data_list = [in_data] @@ -69,7 +67,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): in_data_list = list() representations = in_data.pop("representations") for repr in representations: - in_data_copy = in_data.copy() + in_data_copy = copy.deepcopy(in_data) ext = repr["ext"][1:] subset = in_data_copy["subset"] # filter out non editorial files @@ -87,15 +85,15 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): repr.pop(k) # convert files to list if it isnt - if not isinstance(files, list): + if not isinstance(files, (tuple, list)): files = [files] self.log.debug(f"_ files: {files}") for index, f in enumerate(files): index += 1 # copy dictionaries - in_data_copy = in_data_copy.copy() - repr_new = repr.copy() + in_data_copy = copy.deepcopy(in_data_copy) + repr_new = copy.deepcopy(repr) repr_new["files"] = f repr_new["name"] = ext @@ -118,14 +116,13 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): subset = in_data["subset"] instance = context.create_instance(subset) - instance.data.update( { "subset": subset, - "asset": self.asset_name, + "asset": in_data["asset"], "label": subset, "name": subset, - "family": self.family, + "family": in_data["family"], "version": in_data.get("version", 1), "frameStart": in_data.get("representations", [None])[0].get( "frameStart", None @@ -133,7 +130,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "frameEnd": in_data.get("representations", [None])[0].get( "frameEnd", None ), - "families": self.families + [self.family_preset_key], + "families": self.default_families or [], } ) self.log.info("collected instance: {}".format(pformat(instance.data))) diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index 46471a5fdd..3a63230317 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -32,11 +32,11 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): def process(self, instance): context = instance.context asset_data = instance.data["assetEntity"] - asset = instance.data["asset"] + asset_name = instance.data["asset"] anatomy_data = instance.data["anatomyData"] for subset_name, subset_data in self.subsets.items(): - instance_name = f"{asset}_{subset_name}" + instance_name = f"{asset_name}_{subset_name}" task = subset_data.get("task", "background") # create new instance @@ -47,8 +47,7 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): # add subset data from preset new_instance.data.update(subset_data) - label = f"{instance_name}" - new_instance.data["label"] = label + new_instance.data["label"] = f"{instance_name}" new_instance.data["subset"] = subset_name new_instance.data["families"].append("image") From 302d4cee5bdfc6d2960126d2bc489c9138e87e71 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 18:41:48 +0200 Subject: [PATCH 295/947] do not use `family_preset_key` for multiple_instances check --- pype/plugins/standalonepublisher/publish/collect_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index f0498cdde8..28cbe043ce 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -48,7 +48,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): self.log.debug(f"_ in_data: {pformat(in_data)}") # exception for editorial - if in_data["family_preset_key"] in ["editorial", "psd_batch"]: + if in_data["family"] in ["editorial", "psd_batch"]: in_data_list = self.multiple_instances(context, in_data) else: in_data_list = [in_data] From 9e3419424d5884298c977b326e353f9c78648f24 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 19:04:31 +0200 Subject: [PATCH 296/947] version is updated and added few logs --- .../publish/extract_layout_images.py | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py index c3abf8d36f..48caa9562b 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -2,6 +2,7 @@ import os import copy import pype.api import pyblish.api +from avalon import io PSDImage = None @@ -14,6 +15,7 @@ class ExtractImageForLayout(pype.api.Extractor): 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"] @@ -51,6 +53,7 @@ class ExtractImageForLayout(pype.api.Extractor): 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() @@ -59,21 +62,37 @@ class ExtractImageForLayout(pype.api.Extractor): continue layer_name = layer.name.replace(" ", "_") - instance_name = f"image_{layer_name}" + 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 ("name", "label", "stagingDir"): + 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) ) - new_instance.data["subset"] = instance_name - new_instance.data["anatomyData"]["subset"] = instance_name + # Find latest version + version_number = self.find_latest_version(subset_name, asset_doc) + self.log.info( + "Next version of instance \"{}\" will be {}".format( + instance_name, version_number + ) + ) - # Set `family` + # 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["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() @@ -100,3 +119,24 @@ class ExtractImageForLayout(pype.api.Extractor): } new_instance.data["representations"] = [new_repre] self.log.debug(new_instance.data["name"]) + + def find_latest_version(self, subset_name, asset_doc): + subset_doc = 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 = io.find_one( + { + "type": "version", + "parent": subset_doc["_id"] + }, + sort=[("name", -1)] + ) + if version_doc: + return int(version_doc["name"]) + 1 + return 1 From ac960623735c0c246f3eb6e8eaba890102a62d0d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 19:07:30 +0200 Subject: [PATCH 297/947] added initial version of extract images which only removes the instance ATM --- .../publish/extract_images_for_compositing.py | 136 ++++++++++++++++++ .../publish/extract_layout_images.py | 8 +- 2 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py new file mode 100644 index 0000000000..1693c86d54 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -0,0 +1,136 @@ +import os +import json +import pype.api + +PSDImage = None + + +class ExtractImagesForComp(pype.api.Extractor): + label = "Extract Images for Compositing" + families = ["imageForComp"] + hosts = ["standalonepublisher"] + + # Presetable + allowed_group_names = ["OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide"] + + def process(self, instance): + instance.context.remove(instance) + self.log.info("NOT IMPLEMENTED YET") + return + + # 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!" + ) + + 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 non files without .psd extension + if repre["ext"] != ".psd": + continue + + # 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"] + + # 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 + ) + + with open(json_full_path, "w") as json_filestream: + json.dump(json_data, json_filestream, indent=4) + + instance.data["transfers"] = transfers + instance.data["representations"].append(new_repre) + instance.data["representations"].remove(repre) + + def export_compositing_images(self, psd_object, output_dir, publish_dir): + json_data = { + "__version__": 1, + "children_layers": [] + } + transfers = [] + for main_idx, main_layer in enumerate(psd_object): + if ( + not main_layer.is_visible() + or main_layer.name 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_layers": [] + } + + for layer_idx, layer in export_layers: + 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_layers"].append(layer_data) + + json_data["children_layers"].append(main_layer_data) + + return json_data, transfers diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py index 48caa9562b..7209ec5c0e 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -36,11 +36,11 @@ class ExtractImageForLayout(pype.api.Extractor): return for repre in tuple(repres): - # Skip all non files without .psd extension + # Skip all files without .psd extension if repre["ext"] != ".psd": continue - # TODO add check of list + # 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) @@ -117,8 +117,10 @@ class ExtractImageForLayout(pype.api.Extractor): "files": output_filename, "stagingDir": staging_dir } + self.log.debug( + "Creating new representation: {}".format(new_repre) + ) new_instance.data["representations"] = [new_repre] - self.log.debug(new_instance.data["name"]) def find_latest_version(self, subset_name, asset_doc): subset_doc = io.find_one({ From 5c3159088684f5eef6eb028e94900e2014254b98 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 19:25:20 +0200 Subject: [PATCH 298/947] moved back collect project assets plugin --- .../publish/collect_matching_asset.py | 5 +---- .../publish/collect_project_assets.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 pype/plugins/standalonepublisher/publish/collect_project_assets.py diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index cd69eeefdc..68f07c988d 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -20,11 +20,8 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): self.log.info("Looking for asset document for file \"{}\"".format( instance.data["source"] )) - project_assets = { - asset_doc["name"]: asset_doc - for asset_doc in io.find({"type": "asset"}) - } + project_assets = instance.context.data["projectAssets"] matching_asset_doc = project_assets.get(source_file) if matching_asset_doc is None: for asset_doc in project_assets.values(): diff --git a/pype/plugins/standalonepublisher/publish/collect_project_assets.py b/pype/plugins/standalonepublisher/publish/collect_project_assets.py new file mode 100644 index 0000000000..ced8772b5a --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_project_assets.py @@ -0,0 +1,21 @@ +import pyblish.api +from avalon import io +from pprint import pformat + + +class CollectProjectAssets(pyblish.api.ContextPlugin): + """ + Collect all available project assets to context data. + """ + + label = "Collect Project Assets" + order = pyblish.api.CollectorOrder - 0.1 + hosts = ["standalonepublisher"] + + def process(self, context): + project_assets = { + asset_doc["name"]: asset_doc + for asset_doc in io.find({"type": "asset"}) + } + context.data["projectAssets"] = project_assets + self.log.debug(f"Collected project_assets: {pformat(project_assets)}") From 5ae59c34d122e574977cb6c62444776cc4f7a5af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 19:25:36 +0200 Subject: [PATCH 299/947] collect project assets is only for "image" family --- .../standalonepublisher/publish/collect_project_assets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/standalonepublisher/publish/collect_project_assets.py b/pype/plugins/standalonepublisher/publish/collect_project_assets.py index ced8772b5a..2bb4ca3f39 100644 --- a/pype/plugins/standalonepublisher/publish/collect_project_assets.py +++ b/pype/plugins/standalonepublisher/publish/collect_project_assets.py @@ -11,6 +11,7 @@ class CollectProjectAssets(pyblish.api.ContextPlugin): label = "Collect Project Assets" order = pyblish.api.CollectorOrder - 0.1 hosts = ["standalonepublisher"] + family = ["image"] def process(self, context): project_assets = { From 037ff552e2ef9f5c7e28a5886b2902f0c2895357 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 19:27:13 +0200 Subject: [PATCH 300/947] collect matching assets will raise exception only if instance family is psd_batch --- .../publish/collect_matching_asset.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index 68f07c988d..2c412c9271 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -29,15 +29,18 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): matching_asset_doc = asset_doc break - if not matching_asset_doc: + if matching_asset_doc: + instance.data["asset"] = matching_asset_doc["name"] + instance.data["assetEntity"] = matching_asset_doc + self.log.info( + f"Matching asset found: {pformat(matching_asset_doc)}" + ) + + # QUESTION exception was limited to "psd_batch" family since + # it is required for processing + elif instance.data["family"] == "psd_batch": # TODO better error message raise AssertionError(( "Filename does not contain any name of" " asset documents in database." )) - - instance.data["asset"] = matching_asset_doc["name"] - instance.data["assetEntity"] = matching_asset_doc - self.log.info( - f"Matching asset found: {pformat(matching_asset_doc)}" - ) From 14093da98f6c68b7a3e238d51c9c62cec7c16f2c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 11 Aug 2020 21:55:11 +0100 Subject: [PATCH 301/947] Option to keep the review files. - Disabled by default to maintain backwards compatibility. - Open loading review image sequences into Nuke. --- pype/plugins/maya/create/create_review.py | 2 ++ pype/plugins/maya/publish/extract_playblast.py | 7 +++++-- pype/plugins/nuke/load/load_sequence.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pype/plugins/maya/create/create_review.py b/pype/plugins/maya/create/create_review.py index 3e513032e1..c488a7559c 100644 --- a/pype/plugins/maya/create/create_review.py +++ b/pype/plugins/maya/create/create_review.py @@ -21,4 +21,6 @@ class CreateReview(avalon.maya.Creator): for key, value in animation_data.items(): data[key] = value + data["keepImages"] = False + self.data = data diff --git a/pype/plugins/maya/publish/extract_playblast.py b/pype/plugins/maya/publish/extract_playblast.py index 8d45f98b90..80a4dadf28 100644 --- a/pype/plugins/maya/publish/extract_playblast.py +++ b/pype/plugins/maya/publish/extract_playblast.py @@ -53,7 +53,6 @@ class ExtractPlayblast(pype.api.Extractor): preset['camera'] = camera preset['format'] = "image" - # preset['compression'] = "qt" preset['quality'] = 95 preset['compression'] = "png" preset['start_frame'] = start @@ -102,6 +101,10 @@ class ExtractPlayblast(pype.api.Extractor): if "representations" not in instance.data: instance.data["representations"] = [] + tags = ["review"] + if not instance.data.get("keepImages"): + tags.append("delete") + representation = { 'name': 'png', 'ext': 'png', @@ -111,7 +114,7 @@ class ExtractPlayblast(pype.api.Extractor): "frameEnd": end, 'fps': fps, 'preview': True, - 'tags': ['review', 'delete'] + 'tags': tags } instance.data["representations"].append(representation) diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index aa79d8736a..601e28c7c1 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -70,7 +70,7 @@ def loader_shift(node, frame, relative=True): class LoadSequence(api.Loader): """Load image sequence into Nuke""" - families = ["render2d", "source", "plate", "render", "prerender"] + families = ["render2d", "source", "plate", "render", "prerender", "review"] representations = ["exr", "dpx", "jpg", "jpeg", "png"] label = "Load sequence" From 7c21e2da7f4d57f707d718a816dfa6507f70aab1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 12 Aug 2020 08:13:25 +0100 Subject: [PATCH 302/947] Fix backwards compatibility with legacy switch. --- pype/plugins/maya/create/create_review.py | 2 ++ pype/plugins/maya/publish/collect_review.py | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pype/plugins/maya/create/create_review.py b/pype/plugins/maya/create/create_review.py index 3e513032e1..f05271aeb2 100644 --- a/pype/plugins/maya/create/create_review.py +++ b/pype/plugins/maya/create/create_review.py @@ -21,4 +21,6 @@ class CreateReview(avalon.maya.Creator): for key, value in animation_data.items(): data[key] = value + data["legacy"] = True + self.data = data diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 0575d90452..4d86c6031d 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -69,12 +69,16 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data['remove'] = True i += 1 else: - subset = "{}{}{}".format( - task, - instance.data["subset"][0].upper(), - instance.data["subset"][1:] - ) - instance.data['subset'] = subset + if instance.data.get("legacy", True): + instance.data['subset'] = task + 'Review' + else: + subset = "{}{}{}".format( + task, + instance.data["subset"][0].upper(), + instance.data["subset"][1:] + ) + instance.data['subset'] = subset + instance.data['review_camera'] = camera instance.data['frameStartFtrack'] = instance.data["frameStartHandle"] instance.data['frameEndFtrack'] = instance.data["frameEndHandle"] From c0cfdbbe5da835e5e383765d0a70b1e6b2718e67 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Aug 2020 10:47:08 +0200 Subject: [PATCH 303/947] fix(celaction): new representation name for audio was not accepted --- pype/plugins/celaction/publish/collect_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/celaction/publish/collect_audio.py b/pype/plugins/celaction/publish/collect_audio.py index 610b81d056..c29e212d80 100644 --- a/pype/plugins/celaction/publish/collect_audio.py +++ b/pype/plugins/celaction/publish/collect_audio.py @@ -16,7 +16,7 @@ class AppendCelactionAudio(pyblish.api.ContextPlugin): # get all available representations subsets = pype.get_subsets(asset_entity["name"], - representations=["audio"] + representations=["audio", "wav"] ) self.log.info(f"subsets is: {pformat(subsets)}") From 3bcaeb8d074a7e66087237b61a1916d80624276f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 11:14:24 +0200 Subject: [PATCH 304/947] asset docs for matching asset are queried in the plugin and filtered by selected asset in standalone publisher --- .../publish/collect_matching_asset.py | 36 +++++++++++++++++++ .../publish/collect_project_assets.py | 22 ------------ 2 files changed, 36 insertions(+), 22 deletions(-) delete mode 100644 pype/plugins/standalonepublisher/publish/collect_project_assets.py diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index 2c412c9271..7a193d7087 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -44,3 +44,39 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): "Filename does not contain any name of" " asset documents in database." )) + + def selection_children_by_name(self, instance): + storing_key = "childrenDocsForSelection" + + children_docs = instance.context.data.get(storing_key) + if children_docs is None: + top_asset_doc = instance.context.data["assetEntity"] + assets_by_parent_id = self._asset_docs_by_parent_id(instance) + _children_docs = self._children_docs( + assets_by_parent_id, top_asset_doc + ) + children_docs = { + children_doc["name"].lower(): children_doc + for children_doc in _children_docs + } + instance.context.data[storing_key] = children_docs + return children_docs + + def _children_docs(self, documents_by_parent_id, parent_doc): + # Find all children in reverse order, last children is at first place. + output = [] + children = documents_by_parent_id.get(parent_doc["_id"]) or tuple() + for child in children: + output.extend( + self._children_docs(documents_by_parent_id, child) + ) + output.append(parent_doc) + return output + + def _asset_docs_by_parent_id(self, instance): + # Query all assets for project and store them by parent's id to list + asset_docs_by_parent_id = collections.defaultdict(list) + for asset_doc in io.find({"type": "asset"}): + parent_id = asset_doc["data"]["visualParent"] + asset_docs_by_parent_id[parent_id].append(asset_doc) + return asset_docs_by_parent_id diff --git a/pype/plugins/standalonepublisher/publish/collect_project_assets.py b/pype/plugins/standalonepublisher/publish/collect_project_assets.py deleted file mode 100644 index 2bb4ca3f39..0000000000 --- a/pype/plugins/standalonepublisher/publish/collect_project_assets.py +++ /dev/null @@ -1,22 +0,0 @@ -import pyblish.api -from avalon import io -from pprint import pformat - - -class CollectProjectAssets(pyblish.api.ContextPlugin): - """ - Collect all available project assets to context data. - """ - - label = "Collect Project Assets" - order = pyblish.api.CollectorOrder - 0.1 - hosts = ["standalonepublisher"] - family = ["image"] - - def process(self, context): - project_assets = { - asset_doc["name"]: asset_doc - for asset_doc in io.find({"type": "asset"}) - } - context.data["projectAssets"] = project_assets - self.log.debug(f"Collected project_assets: {pformat(project_assets)}") From 5ac4aa100abf1f1f85c637abcd3ee18fb9215094 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 11:15:01 +0200 Subject: [PATCH 305/947] source file basename and asset names are lowered for match check --- .../publish/collect_matching_asset.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index 7a193d7087..3f28b610e0 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -1,4 +1,5 @@ import os +import collections import pyblish.api from avalon import io from pprint import pformat @@ -16,16 +17,17 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): family = ["image"] def process(self, instance): - source_file = os.path.basename(instance.data["source"]) + source_file = os.path.basename(instance.data["source"]).lower() self.log.info("Looking for asset document for file \"{}\"".format( instance.data["source"] )) - project_assets = instance.context.data["projectAssets"] - matching_asset_doc = project_assets.get(source_file) + asset_docs_by_name = self.selection_children_by_name(instance) + + matching_asset_doc = asset_docs_by_name.get(source_file) if matching_asset_doc is None: - for asset_doc in project_assets.values(): - if asset_doc["name"] in source_file: + for asset_name_low, asset_doc in asset_docs_by_name.items(): + if asset_name_low in source_file: matching_asset_doc = asset_doc break @@ -41,9 +43,9 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): elif instance.data["family"] == "psd_batch": # TODO better error message raise AssertionError(( - "Filename does not contain any name of" - " asset documents in database." - )) + "Filename \"{}\" does not match" + " any name of asset documents in database for your selection." + ).format(instance.data["source"])) def selection_children_by_name(self, instance): storing_key = "childrenDocsForSelection" From 91bc270495e5833386e6f4dd902cf1e7603ed6e0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Aug 2020 10:54:03 +0100 Subject: [PATCH 306/947] celaction fixes --- pype/hooks/celaction/prelaunch.py | 2 +- pype/lib.py | 4 +- .../publish/collect_celaction_instances.py | 2 +- .../publish/submit_celaction_deadline.py | 2 + pype/plugins/global/publish/cleanup.py | 102 +++++++++++++----- pype/plugins/global/publish/extract_review.py | 27 +++-- .../collect_instance_representations.py | 2 +- 7 files changed, 101 insertions(+), 40 deletions(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index e1e86cc919..58b1ac8a23 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -107,7 +107,7 @@ class CelactionPrelaunchHook(PypeHook): f"--asset {asset}", f"--task {task}", "--currentFile \\\"\"*SCENE*\"\\\"", - "--chunk *CHUNK*", + "--chunk 10", "--frameStart *START*", "--frameEnd *END*", "--resolutionWidth *X*", diff --git a/pype/lib.py b/pype/lib.py index 87808e53f5..6d7caf0af4 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -110,7 +110,9 @@ def _subprocess(*args, **kwargs): log.error(line) if proc.returncode != 0: - raise ValueError("\"{}\" was not successful: {}".format(args, output)) + raise ValueError( + "\"{}\" was not successful:\nOutput: {}\nError: {}".format( + args, output, error)) return output diff --git a/pype/plugins/celaction/publish/collect_celaction_instances.py b/pype/plugins/celaction/publish/collect_celaction_instances.py index 431ab722d3..d3d1d264c0 100644 --- a/pype/plugins/celaction/publish/collect_celaction_instances.py +++ b/pype/plugins/celaction/publish/collect_celaction_instances.py @@ -52,7 +52,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): "subset": subset, "label": scene_file, "family": family, - "families": [family], + "families": [family, "ftrack"], "representations": list() }) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 9091b24150..e03c6a3550 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -141,6 +141,8 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): # # Asset dependency to wait for at least the scene file to sync. # "AssetDependency0": script_path + "ScheduledType": "Once", + "JobDelay": "00:00:08:00" }, "PluginInfo": { # Input diff --git a/pype/plugins/global/publish/cleanup.py b/pype/plugins/global/publish/cleanup.py index 3ab41f90ca..77fef882b5 100644 --- a/pype/plugins/global/publish/cleanup.py +++ b/pype/plugins/global/publish/cleanup.py @@ -1,29 +1,7 @@ import os import shutil import pyblish.api - - -def clean_renders(instance): - transfers = instance.data.get("transfers", list()) - - current_families = instance.data.get("families", list()) - instance_family = instance.data.get("family", None) - dirnames = [] - - for src, dest in transfers: - if os.path.normpath(src) != os.path.normpath(dest): - if instance_family == 'render' or 'render' in current_families: - os.remove(src) - dirnames.append(os.path.dirname(src)) - - # make unique set - cleanup_dirs = set(dirnames) - for dir in cleanup_dirs: - try: - os.rmdir(dir) - except OSError: - # directory is not empty, skipping - continue +import re class CleanUp(pyblish.api.InstancePlugin): @@ -39,6 +17,9 @@ class CleanUp(pyblish.api.InstancePlugin): optional = True active = True + # Presets + paterns = None # list of regex paterns + def process(self, instance): # Get the errored instances failed = [] @@ -52,8 +33,8 @@ class CleanUp(pyblish.api.InstancePlugin): ) ) - self.log.info("Cleaning renders ...") - clean_renders(instance) + self.log.info("Cleaning renders new...") + self.clean_renders(instance) if [ef for ef in self.exclude_families if instance.data["family"] in ef]: @@ -73,3 +54,74 @@ class CleanUp(pyblish.api.InstancePlugin): self.log.info("Removing staging directory ...") shutil.rmtree(staging_dir) + + def clean_renders(self, instance): + transfers = instance.data.get("transfers", list()) + + current_families = instance.data.get("families", list()) + instance_family = instance.data.get("family", None) + dirnames = [] + transfers_dirs = [] + + for src, dest in transfers: + # fix path inconsistency + src = os.path.normpath(src) + dest = os.path.normpath(dest) + + # add src dir into clearing dir paths (regex paterns) + transfers_dirs.append(os.path.dirname(src)) + + # add dest dir into clearing dir paths (regex paterns) + transfers_dirs.append(os.path.dirname(dest)) + + if os.path.normpath(src) != os.path.normpath(dest): + if instance_family == 'render' or 'render' in current_families: + self.log.info("Removing src: `{}`...".format(src)) + os.remove(src) + + # add dir for cleanup + dirnames.append(os.path.dirname(src)) + + # clean by regex paterns + # make unique set + transfers_dirs = set(transfers_dirs) + + self.log.debug("__ transfers_dirs: `{}`".format(transfers_dirs)) + self.log.debug("__ self.paterns: `{}`".format(self.paterns)) + if self.paterns: + files = list() + # get list of all available content of dirs + for _dir in transfers_dirs: + if not os.path.exists(_dir): + continue + files.extend([ + os.path.join(_dir, f) + for f in os.listdir(_dir)]) + + self.log.debug("__ files: `{}`".format(files)) + + # remove all files which match regex patern + for f in files: + for p in self.paterns: + patern = re.compile(p) + if not patern.findall(f): + continue + if not os.path.exists(f): + continue + + self.log.info("Removing file by regex: `{}`".format(f)) + os.remove(f) + + # add dir for cleanup + dirnames.append(os.path.dirname(f)) + + # make unique set + cleanup_dirs = set(dirnames) + + # clean dirs which are empty + for dir in cleanup_dirs: + try: + os.rmdir(dir) + except OSError: + # directory is not empty, skipping + continue diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index a16c3ce256..e1a0d7043a 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -119,6 +119,9 @@ class ExtractReview(pyblish.api.InstancePlugin): if "review" not in tags or "thumbnail" in tags: continue + if "passing" in tags: + continue + input_ext = repre["ext"] if input_ext.startswith("."): input_ext = input_ext[1:] @@ -1314,7 +1317,8 @@ class ExtractReview(pyblish.api.InstancePlugin): output_args.extend(profile.get('output', [])) # defining image ratios - resolution_ratio = (float(resolution_width) * pixel_aspect) / resolution_height + resolution_ratio = ( + float(resolution_width) * pixel_aspect) / resolution_height delivery_ratio = float(self.to_width) / float(self.to_height) self.log.debug( "__ resolution_ratio: `{}`".format(resolution_ratio)) @@ -1371,7 +1375,8 @@ class ExtractReview(pyblish.api.InstancePlugin): output_args.append("-shortest") if no_handles: - duration_sec = float(frame_end_handle - frame_start_handle + 1) / fps + duration_sec = float( + frame_end_handle - frame_start_handle + 1) / fps output_args.append("-t {:0.2f}".format(duration_sec)) @@ -1393,7 +1398,7 @@ class ExtractReview(pyblish.api.InstancePlugin): self.log.debug("lower then delivery") width_scale = int(self.to_width * scale_factor) width_half_pad = int(( - self.to_width - width_scale)/2) + self.to_width - width_scale) / 2) height_scale = self.to_height height_half_pad = 0 else: @@ -1408,7 +1413,7 @@ class ExtractReview(pyblish.api.InstancePlugin): height_scale = int( resolution_height * scale_factor) height_half_pad = int( - (self.to_height - height_scale)/2) + (self.to_height - height_scale) / 2) self.log.debug( "__ width_scale: `{}`".format(width_scale)) @@ -1425,11 +1430,11 @@ class ExtractReview(pyblish.api.InstancePlugin): scaling_arg = str( "scale={0}x{1}:flags=lanczos," "pad={2}:{3}:{4}:{5}:black,setsar=1" - ).format(width_scale, height_scale, - self.to_width, self.to_height, - width_half_pad, - height_half_pad - ) + ).format(width_scale, height_scale, + self.to_width, self.to_height, + width_half_pad, + height_half_pad + ) vf_back = self.add_video_filter_args( output_args, scaling_arg) @@ -1449,7 +1454,7 @@ class ExtractReview(pyblish.api.InstancePlugin): lut_arg = "lut3d=file='{}'".format( lut_path.replace( "\\", "/").replace(":/", "\\:/") - ) + ) lut_arg += ",colormatrix=bt601:bt709" vf_back = self.add_video_filter_args( @@ -1504,7 +1509,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "outputName": name + "_noHandles", "frameStartFtrack": frame_start, "frameEndFtrack": frame_end - }) + }) if repre_new.get('preview'): repre_new.pop("preview") if repre_new.get('thumbnail'): diff --git a/pype/plugins/premiere/publish/collect_instance_representations.py b/pype/plugins/premiere/publish/collect_instance_representations.py index b62b47c473..a7aa033f02 100644 --- a/pype/plugins/premiere/publish/collect_instance_representations.py +++ b/pype/plugins/premiere/publish/collect_instance_representations.py @@ -53,7 +53,7 @@ class CollectClipRepresentations(pyblish.api.InstancePlugin): "fps": fps, "name": json_repr_subset, "ext": json_repr_ext, - "tags": ["review", "delete"] + "tags": ["review", "passing", "ftrackreview"] } else: representation = { From 58cd5f8c7ef9dccca6967a913069344bf5734a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 12 Aug 2020 12:06:40 +0200 Subject: [PATCH 307/947] fix invalid scope in validate scene settings --- pype/plugins/harmony/publish/validate_scene_settings.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index 3602f1ca22..d7895804bd 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -40,13 +40,11 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): expected_settings["frameEnd"] = frame_end - frame_start + 1 expected_settings["frameStart"] = 1 - - self.log.info(instance.context.data['anatomyData']['asset']) if any(string in instance.context.data['anatomyData']['asset'] - for string in frame_check_filter): - expected_settings.pop("frameEnd") + for string in self.frame_check_filter): + expected_settings.pop("frameEnd") func = """function func() { From b5cab8358faf18b280d7b9e2f671cb12d593f617 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 12:18:01 +0200 Subject: [PATCH 308/947] extractor for compositing enhanced --- .../publish/extract_images_for_compositing.py | 55 ++++++++++++++++++- .../publish/extract_layout_images.py | 4 +- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index 1693c86d54..52293a8407 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -1,5 +1,6 @@ import os import json +import copy import pype.api PSDImage = None @@ -10,6 +11,8 @@ class ExtractImagesForComp(pype.api.Extractor): families = ["imageForComp"] hosts = ["standalonepublisher"] + new_instance_family = "image" + # Presetable allowed_group_names = ["OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide"] @@ -27,11 +30,16 @@ class ExtractImagesForComp(pype.api.Extractor): "BUG: Python module `psd-tools` is not installed!" ) + 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"] = [] + for repre in tuple(repres): # Skip all non files without .psd extension if repre["ext"] != ".psd": @@ -73,7 +81,7 @@ class ExtractImagesForComp(pype.api.Extractor): with open(json_full_path, "w") as json_filestream: json.dump(json_data, json_filestream, indent=4) - instance.data["transfers"] = transfers + instance.data["transfers"].extend(transfers) instance.data["representations"].append(new_repre) instance.data["representations"].remove(repre) @@ -134,3 +142,48 @@ class ExtractImagesForComp(pype.api.Extractor): json_data["children_layers"].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 + + # Same data apply to anatomy data + instance.data["anatomyData"].update({ + "family": self.new_instance_family, + }) + + # 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)) diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py index 7209ec5c0e..eb27a5a568 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -76,7 +76,7 @@ class ExtractImageForLayout(pype.api.Extractor): ) # Find latest version - version_number = self.find_latest_version(subset_name, asset_doc) + version_number = self.find_next_version(subset_name, asset_doc) self.log.info( "Next version of instance \"{}\" will be {}".format( instance_name, version_number @@ -122,7 +122,7 @@ class ExtractImageForLayout(pype.api.Extractor): ) new_instance.data["representations"] = [new_repre] - def find_latest_version(self, subset_name, asset_doc): + def find_next_version(self, subset_name, asset_doc): subset_doc = io.find_one({ "type": "subset", "name": subset_name, From 96f7cb395979d293fb1fe514e329b56e3e16ccd4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Aug 2020 13:50:15 +0200 Subject: [PATCH 309/947] Fix conflicts from 'develop' --- pype/api.py | 22 ++++- pype/lib.py | 84 +++++++++++++++++--- pype/plugins/global/publish/integrate_new.py | 46 ++++++----- 3 files changed, 122 insertions(+), 30 deletions(-) diff --git a/pype/api.py b/pype/api.py index 0cf2573298..c722757a3c 100644 --- a/pype/api.py +++ b/pype/api.py @@ -6,6 +6,14 @@ from pypeapp import ( execute ) +from pypeapp.lib.mongo import ( + decompose_url, + compose_url, + get_default_components +) + +from . import resources + from .plugin import ( Extractor, @@ -30,9 +38,11 @@ from .lib import ( get_hierarchy, get_subsets, get_version_from_path, + get_last_version_from_path, modified_environ, add_tool_to_environment, - source_hash + source_hash, + get_latest_version ) # Special naming case for subprocess since its a built-in method. @@ -44,6 +54,12 @@ __all__ = [ "project_overrides_dir_path", "config", "execute", + "decompose_url", + "compose_url", + "get_default_components", + + # Resources + "resources", # plugin classes "Extractor", @@ -68,9 +84,11 @@ __all__ = [ "get_asset", "get_subsets", "get_version_from_path", + "get_last_version_from_path", "modified_environ", "add_tool_to_environment", "source_hash", - "subprocess" + "subprocess", + "get_latest_version" ] diff --git a/pype/lib.py b/pype/lib.py index 006a396720..ec0fb84f84 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -469,6 +469,43 @@ def get_version_from_path(file): ) +def get_last_version_from_path(path_dir, filter): + """ + Finds last version of given directory content + + Args: + path_dir (string): directory path + filter (list): list of strings used as file name filter + + Returns: + string: file name with last version + + Example: + last_version_file = get_last_version_from_path( + "/project/shots/shot01/work", ["shot01", "compositing", "nk"]) + """ + + assert os.path.isdir(path_dir), "`path_dir` argument needs to be directory" + assert isinstance(filter, list) and ( + len(filter) != 0), "`filter` argument needs to be list and not empty" + + filtred_files = list() + + # form regex for filtering + patern = r".*".join(filter) + + for f in os.listdir(path_dir): + if not re.findall(patern, f): + continue + filtred_files.append(f) + + if filtred_files: + sorted(filtred_files) + return filtred_files[-1] + else: + return None + + def get_avalon_database(): if io._database is None: set_io_database() @@ -482,14 +519,6 @@ def set_io_database(): io.install() -def get_all_avalon_projects(): - db = get_avalon_database() - projects = [] - for name in db.collection_names(): - projects.append(db[name].find_one({'type': 'project'})) - return projects - - def filter_pyblish_plugins(plugins): """ This servers as plugin filter / modifier for pyblish. It will load plugin @@ -610,7 +639,7 @@ def get_subsets(asset_name, if len(repres_out) > 0: output_dict[subset["name"]] = {"version": version_sel, - "representaions": repres_out} + "representations": repres_out} return output_dict @@ -1350,7 +1379,6 @@ def ffprobe_streams(path_to_file): log.debug("FFprobe output: {}".format(popen_output)) return json.loads(popen_output)["streams"] - def source_hash(filepath, *args): """Generate simple identifier for a source file. This is used to identify whether a source file has previously been @@ -1370,3 +1398,39 @@ def source_hash(filepath, *args): time = str(os.path.getmtime(filepath)) size = str(os.path.getsize(filepath)) return "|".join([file_name, time, size] + list(args)).replace(".", ",") + +def get_latest_version(asset_name, subset_name): + """Retrieve latest version from `asset_name`, and `subset_name`. + + Args: + asset_name (str): Name of asset. + subset_name (str): Name of subset. + """ + # Get asset + asset_name = io.find_one( + {"type": "asset", "name": asset_name}, projection={"name": True} + ) + + subset = io.find_one( + {"type": "subset", "name": subset_name, "parent": asset_name["_id"]}, + projection={"_id": True, "name": True}, + ) + + # Check if subsets actually exists. + assert subset, "No subsets found." + + # Get version + version_projection = { + "name": True, + "parent": True, + } + + version = io.find_one( + {"type": "version", "parent": subset["_id"]}, + projection=version_projection, + sort=[("name", -1)], + ) + + assert version, "No version found, this is a bug" + + return version diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 5c48770f99..2cf09474b8 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -84,7 +84,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "fbx", "textures", "action", - "harmony.template" + "harmony.template", + "harmony.palette", + "editorial" ] exclude_families = ["clip"] db_representation_context_keys = [ @@ -111,7 +113,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.info("instance.data: {}".format(instance.data)) self.handle_destination_files(self.integrated_file_sizes, 'finalize') - except Exception as e: + except Exception: # clean destination self.log.critical("Error when registering", exc_info=True) self.handle_destination_files(self.integrated_file_sizes, 'remove') @@ -155,6 +157,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if task_name: anatomy_data["task"] = task_name + anatomy_data["family"] = instance.data.get("family") + stagingdir = instance.data.get("stagingDir") if not stagingdir: self.log.info(( @@ -398,8 +402,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst = "{0}{1}{2}".format( dst_head, dst_padding, - dst_tail - ).replace("..", ".") + dst_tail).replace("..", ".") self.log.debug("destination: `{}`".format(dst)) src = os.path.join(stagingdir, src_file_name) @@ -606,12 +609,19 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # copy file with speedcopy and check if size of files are simetrical while True: + import shutil try: copyfile(src, dst) - except OSError as e: - self.log.critical("Cannot copy {} to {}".format(src, dst)) - self.log.critical(e) - six.reraise(*sys.exc_info()) + except shutil.SameFileError as sfe: + self.log.critical("files are the same {} to {}".format(src, dst)) + os.remove(dst) + try: + shutil.copyfile(src, dst) + self.log.debug("Copying files with shutil...") + except (OSError) as e: + self.log.critical("Cannot copy {} to {}".format(src, dst)) + self.log.critical(e) + six.reraise(*sys.exc_info()) if str(getsize(src)) in str(getsize(dst)): break @@ -648,7 +658,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "type": "subset", "name": subset_name, "data": { - "families": instance.data.get('families') + "families": instance.data.get("families", []) }, "parent": asset["_id"] }).inserted_id @@ -761,7 +771,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): task_name = io.Session.get("AVALON_TASK") family = self.main_family_from_instance(instance) - matching_profiles = None + matching_profiles = {} highest_value = -1 self.log.info(self.template_name_profiles) for name, filters in self.template_name_profiles.items(): @@ -860,13 +870,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for src, dest in resources: path = self.get_rootless_path(anatomy, dest) dest = self.get_dest_temp_url(dest) - hash = pype.api.source_hash(dest) - if self.TMP_FILE_EXT and ',{}'.format(self.TMP_FILE_EXT) in hash: - hash = hash.replace(',{}'.format(self.TMP_FILE_EXT), '') + file_hash = pype.api.source_hash(dest) + if self.TMP_FILE_EXT and ',{}'.format(self.TMP_FILE_EXT) in file_hash: + file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), '') file_info = self.prepare_file_info(path, integrated_file_sizes[dest], - hash) + file_hash) output_resources.append(file_info) return output_resources @@ -885,13 +895,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dest += '.{}'.format(self.TMP_FILE_EXT) return dest - def prepare_file_info(self, path, size=None, hash=None, sites=None): + def prepare_file_info(self, path, size=None, file_hash=None, sites=None): """ Prepare information for one file (asset or resource) Arguments: path: destination url of published file (rootless) size(optional): size of file in bytes - hash(optional): hash of file for synchronization validation + file_hash(optional): hash of file for synchronization validation sites(optional): array of published locations, ['studio'] by default expected ['studio', 'site1', 'gdrive1'] Returns: @@ -905,8 +915,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if size: rec["size"] = size - if hash: - rec["hash"] = hash + if file_hash: + rec["hash"] = file_hash if sites: rec["sites"] = sites From d37eac4a226444fa5b49798f6e8b39d8b6303219 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Aug 2020 13:51:28 +0200 Subject: [PATCH 310/947] Modified 'sites' element to object of objects for better syncing --- pype/plugins/global/publish/integrate_new.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 2cf09474b8..fb1588f7d8 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -12,6 +12,7 @@ import pyblish.api from avalon import io from avalon.vendor import filelink import pype.api +from datetime import datetime # this is needed until speedcopy for linux is fixed if sys.platform == "win32": @@ -902,13 +903,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): path: destination url of published file (rootless) size(optional): size of file in bytes file_hash(optional): hash of file for synchronization validation - sites(optional): array of published locations, ['studio'] by default - expected ['studio', 'site1', 'gdrive1'] + sites(optional): array of published locations, ['studio': {'created_dt':date}] by default + keys expected ['studio', 'site1', 'gdrive1'] Returns: rec: dictionary with filled info """ - rec = { # TODO update collection step to extend with necessary values + rec = { "_id": io.ObjectId(), "path": path } @@ -921,7 +922,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if sites: rec["sites"] = sites else: - rec["sites"] = ["studio"] + meta = {"created_dt": datetime.now()} + rec["sites"] = {"studio": meta} return rec From 75efbd9a51e29a3b2f278f5fa477eb7dc1d65dac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 14:21:08 +0200 Subject: [PATCH 311/947] skip layers without content --- .../standalonepublisher/publish/extract_layout_images.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py index eb27a5a568..95eaaaf967 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -61,6 +61,14 @@ class ExtractImageForLayout(pype.api.Extractor): ): 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( From b7148c08947ca8acd20c6658f34e6562fa7a56e0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 12 Aug 2020 14:21:26 +0200 Subject: [PATCH 312/947] Hound --- pype/lib.py | 2 + pype/plugins/global/publish/integrate_new.py | 42 +++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index ec0fb84f84..1dd5dd9de5 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -1379,6 +1379,7 @@ def ffprobe_streams(path_to_file): log.debug("FFprobe output: {}".format(popen_output)) return json.loads(popen_output)["streams"] + def source_hash(filepath, *args): """Generate simple identifier for a source file. This is used to identify whether a source file has previously been @@ -1399,6 +1400,7 @@ def source_hash(filepath, *args): size = str(os.path.getsize(filepath)) return "|".join([file_name, time, size] + list(args)).replace(".", ",") + def get_latest_version(asset_name, subset_name): """Retrieve latest version from `asset_name`, and `subset_name`. diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index fb1588f7d8..88267e1b0a 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -116,7 +116,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): 'finalize') except Exception: # clean destination - self.log.critical("Error when registering", exc_info=True) + self.log.critical("Error when registering", exc_info=True) self.handle_destination_files(self.integrated_file_sizes, 'remove') six.reraise(*sys.exc_info()) @@ -515,8 +515,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # get 'files' info for representation and all attached resources self.log.debug("Preparing files information ...") representation["files"] = self.get_files_info( - instance, - self.integrated_file_sizes) + instance, + self.integrated_file_sizes) self.log.debug("__ representation: {}".format(representation)) destination_list.append(dst) @@ -613,13 +613,14 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): import shutil try: copyfile(src, dst) - except shutil.SameFileError as sfe: - self.log.critical("files are the same {} to {}".format(src, dst)) + except shutil.SameFileError: + self.log.critical("files are the same {} to {}".format(src, + dst)) os.remove(dst) try: shutil.copyfile(src, dst) self.log.debug("Copying files with shutil...") - except (OSError) as e: + except OSError as e: self.log.critical("Cannot copy {} to {}".format(src, dst)) self.log.critical(e) six.reraise(*sys.exc_info()) @@ -841,9 +842,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): path = rootless_path else: self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(path)) + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(path)) return path def get_files_info(self, instance, integrated_file_sizes): @@ -864,16 +865,19 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): resources = list(instance.data.get("transfers", [])) resources.extend(list(instance.data.get("hardlinks", []))) - self.log.debug("get_resource_files_info.resources:{}".format(resources)) + self.log.debug("get_resource_files_info.resources:{}". + format(resources)) output_resources = [] anatomy = instance.context.data["anatomy"] - for src, dest in resources: + for _src, dest in resources: path = self.get_rootless_path(anatomy, dest) dest = self.get_dest_temp_url(dest) file_hash = pype.api.source_hash(dest) - if self.TMP_FILE_EXT and ',{}'.format(self.TMP_FILE_EXT) in file_hash: - file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), '') + if self.TMP_FILE_EXT and \ + ',{}'.format(self.TMP_FILE_EXT) in file_hash: + file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), + '') file_info = self.prepare_file_info(path, integrated_file_sizes[dest], @@ -883,7 +887,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): return output_resources def get_dest_temp_url(self, dest): - """ Enhance destination path with TMP_FILE_EXT to denote temporary file. + """ Enhance destination path with TMP_FILE_EXT to denote temporary + file. Temporary files will be renamed after successful registration into DB and full copy to destination @@ -903,7 +908,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): path: destination url of published file (rootless) size(optional): size of file in bytes file_hash(optional): hash of file for synchronization validation - sites(optional): array of published locations, ['studio': {'created_dt':date}] by default + sites(optional): array of published locations, + ['studio': {'created_dt':date}] by default keys expected ['studio', 'site1', 'gdrive1'] Returns: rec: dictionary with filled info @@ -941,7 +947,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): remove TMP_FILE_EXT suffix denoting temp file """ if integrated_file_sizes: - for file_url, file_size in integrated_file_sizes.items(): + for file_url, _file_size in integrated_file_sizes.items(): try: if mode == 'remove': self.log.debug("Removing file ...{}".format(file_url)) @@ -958,6 +964,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): except FileNotFoundError: pass # file not there, nothing to delete except OSError: - self.log.error("Cannot {} file {}".format(mode, file_url) - , exc_info=True) + self.log.error("Cannot {} file {}".format(mode, file_url), + exc_info=True) six.reraise(*sys.exc_info()) From 46626de9731c6d3fa0e0874210f220a0207266ea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 14:28:29 +0200 Subject: [PATCH 313/947] set imageForComp subset to unchecked by sefault --- .../publish/collect_psd_instances.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index 3a63230317..f908012fdc 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -26,8 +26,9 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): "workfileBackground": { "task": "background", "family": "workfile" - }, + } } + unchecked_by_default = ["imageForComp"] def process(self, instance): context = instance.context @@ -41,6 +42,7 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): # create new instance new_instance = context.create_instance(instance_name) + # add original instance data except name key new_instance.data.update({k: v for k, v in instance.data.items() if k not in "name"}) @@ -61,11 +63,11 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): }) new_instance.data["anatomyData"] = anatomy_data_new - inst_data = new_instance.data - self.log.debug( - f"_ inst_data: {pformat(inst_data)}") + 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) From cef7e87b01248ee95af42fb4937ecd0fb30db78b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 14:44:15 +0200 Subject: [PATCH 314/947] fix hierarchy for psd --- .../plugins/global/publish/collect_anatomy_instance_data.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/plugins/global/publish/collect_anatomy_instance_data.py b/pype/plugins/global/publish/collect_anatomy_instance_data.py index 6528bede2e..806c71eb5f 100644 --- a/pype/plugins/global/publish/collect_anatomy_instance_data.py +++ b/pype/plugins/global/publish/collect_anatomy_instance_data.py @@ -92,6 +92,12 @@ class CollectAnatomyInstanceData(pyblish.api.InstancePlugin): "subset": subset_name, "version": version_number } + if ( + asset_entity + and asset_entity["_id"] != context_asset_entity["_id"] + ): + parents = asset_entity["data"].get("parents") or list() + anatomy_updates["hierarchy"] = "/".join(parents) task_name = instance.data.get("task") if task_name: From c53847d0fad57ca557361dd49b61fe554a453075 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 14:48:32 +0200 Subject: [PATCH 315/947] add check for instance assetEntity --- .../global/publish/collect_anatomy_instance_data.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_anatomy_instance_data.py b/pype/plugins/global/publish/collect_anatomy_instance_data.py index 806c71eb5f..44a4d43946 100644 --- a/pype/plugins/global/publish/collect_anatomy_instance_data.py +++ b/pype/plugins/global/publish/collect_anatomy_instance_data.py @@ -39,11 +39,21 @@ class CollectAnatomyInstanceData(pyblish.api.InstancePlugin): anatomy_data = copy.deepcopy(instance.context.data["anatomyData"]) project_entity = instance.context.data["projectEntity"] context_asset_entity = instance.context.data["assetEntity"] + instance_asset_entity = instance.data.get("assetEntity") asset_name = instance.data["asset"] + + # There is possibility that assetEntity on instance is already set + # which can happen in standalone publisher + if ( + instance_asset_entity + and instance_asset_entity["name"] == asset_name + ): + asset_entity = instance_asset_entity + # Check if asset name is the same as what is in context # - they may be different, e.g. in NukeStudio - if context_asset_entity["name"] == asset_name: + elif context_asset_entity["name"] == asset_name: asset_entity = context_asset_entity else: From b5a27e11210a317eaa0ee089d899b6c761a926fe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 15:09:24 +0200 Subject: [PATCH 316/947] fixed version for extract images for compositing --- .../publish/extract_images_for_compositing.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index 52293a8407..c293783a53 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -2,6 +2,7 @@ import os import json import copy import pype.api +from avalon import io PSDImage = None @@ -77,13 +78,13 @@ class ExtractImagesForComp(pype.api.Extractor): 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"].append(new_repre) instance.data["representations"].remove(repre) + instance.data["representations"].append(new_repre) def export_compositing_images(self, psd_object, output_dir, publish_dir): json_data = { @@ -161,9 +162,21 @@ class ExtractImagesForComp(pype.api.Extractor): # 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) + if latest_version is None: + latest_version = 1 + else: + version_number = latest_version + 1 + + 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 @@ -187,3 +200,24 @@ class ExtractImagesForComp(pype.api.Extractor): 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 = 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 = io.find_one( + { + "type": "version", + "parent": subset_doc["_id"] + }, + sort=[("name", -1)] + ) + if version_doc: + return int(version_doc["name"]) + return None From 8beda6e35358945ff51c977231f3e0b8b331b836 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 15:14:08 +0200 Subject: [PATCH 317/947] fixed latest version for extract layout --- .../publish/extract_images_for_compositing.py | 7 +++---- .../publish/extract_layout_images.py | 13 +++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index c293783a53..a347620be9 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -165,10 +165,9 @@ class ExtractImagesForComp(pype.api.Extractor): subset_name = instance.data["anatomyData"]["subset"] asset_doc = instance.data["assetEntity"] latest_version = self.find_last_version(subset_name, asset_doc) - if latest_version is None: - latest_version = 1 - else: - version_number = latest_version + 1 + version_number = 1 + if latest_version is not None: + version_number += latest_version instance.data["latestVersion"] = latest_version instance.data["version"] = version_number diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py index 95eaaaf967..a69a08f0d8 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -84,7 +84,11 @@ class ExtractImageForLayout(pype.api.Extractor): ) # Find latest version - version_number = self.find_next_version(subset_name, asset_doc) + 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 @@ -95,6 +99,7 @@ class ExtractImageForLayout(pype.api.Extractor): 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, @@ -130,7 +135,7 @@ class ExtractImageForLayout(pype.api.Extractor): ) new_instance.data["representations"] = [new_repre] - def find_next_version(self, subset_name, asset_doc): + def find_last_version(self, subset_name, asset_doc): subset_doc = io.find_one({ "type": "subset", "name": subset_name, @@ -148,5 +153,5 @@ class ExtractImageForLayout(pype.api.Extractor): sort=[("name", -1)] ) if version_doc: - return int(version_doc["name"]) + 1 - return 1 + return int(version_doc["name"]) + return None From ba73ba7b6cd5aa86d094e51edf13df1e6bafab75 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 15:22:13 +0200 Subject: [PATCH 318/947] renamed `children_layers` key to `children` --- .../publish/extract_images_for_compositing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index a347620be9..47d21a1af0 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -89,7 +89,7 @@ class ExtractImagesForComp(pype.api.Extractor): def export_compositing_images(self, psd_object, output_dir, publish_dir): json_data = { "__version__": 1, - "children_layers": [] + "children": [] } transfers = [] for main_idx, main_layer in enumerate(psd_object): @@ -119,7 +119,7 @@ class ExtractImagesForComp(pype.api.Extractor): main_layer_data = { "index": main_idx, "name": main_layer.name, - "children_layers": [] + "children": [] } for layer_idx, layer in export_layers: @@ -138,9 +138,9 @@ class ExtractImagesForComp(pype.api.Extractor): pil_object = layer.composite(viewport=psd_object.viewbox) pil_object.save(output_filepath, "PNG") - main_layer_data["children_layers"].append(layer_data) + main_layer_data["children"].append(layer_data) - json_data["children_layers"].append(main_layer_data) + json_data["children"].append(main_layer_data) return json_data, transfers From aed3f4059e7f73a0bb5dfb802105a85cf9e7e106 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 15:41:17 +0200 Subject: [PATCH 319/947] make sure new instances does not share object values --- .../standalonepublisher/publish/collect_psd_instances.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index f908012fdc..9369822df2 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -44,8 +44,12 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): new_instance = context.create_instance(instance_name) # add original instance data except name key - new_instance.data.update({k: v for k, v in instance.data.items() - if k not in "name"}) + 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) From e27f4b74349abec0394a0f003cac3508fd2b67c8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 15:44:55 +0200 Subject: [PATCH 320/947] make extor for compositing available --- .../standalonepublisher/publish/collect_psd_instances.py | 2 +- .../publish/extract_images_for_compositing.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index 9369822df2..f7ea30e810 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -28,7 +28,7 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): "family": "workfile" } } - unchecked_by_default = ["imageForComp"] + unchecked_by_default = [] def process(self, instance): context = instance.context diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index 47d21a1af0..87d6aa13cb 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -18,10 +18,6 @@ class ExtractImagesForComp(pype.api.Extractor): allowed_group_names = ["OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide"] def process(self, instance): - instance.context.remove(instance) - self.log.info("NOT IMPLEMENTED YET") - return - # Check if python module `psd_tools` is installed try: global PSDImage From 4bf91e026f24ddc762cad96598e38f31c30643de Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 15:54:16 +0200 Subject: [PATCH 321/947] allow to enable/disable logs suspending on stard with env variable PYBLISH_SUSPEND_LOGS --- pype/tools/pyblish_pype/util.py | 9 +++++++++ pype/tools/pyblish_pype/window.py | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pype/tools/pyblish_pype/util.py b/pype/tools/pyblish_pype/util.py index d10e7a002a..5a4dbfb250 100644 --- a/pype/tools/pyblish_pype/util.py +++ b/pype/tools/pyblish_pype/util.py @@ -309,3 +309,12 @@ class OrderGroups: return group_range return float(group_range) + + +def env_variable_to_bool(env_key): + value = os.environ.get(env_key) + if value is not None: + value = value.lower() + if value in ("true", "1", "yes"): + return True + return False diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 7d79e0e26c..723069f074 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -55,6 +55,7 @@ class Window(QtWidgets.QDialog): super(Window, self).__init__(parent=parent) self._suspend_logs = False + # Use plastique style for specific ocations # TODO set style name via environment variable low_keys = { @@ -511,6 +512,10 @@ class Window(QtWidgets.QDialog): self.tabs[current_page].setChecked(True) + self.apply_log_suspend_value( + util.env_variable_to_bool("PYBLISH_SUSPEND_LOGS") + ) + # ------------------------------------------------------------------------- # # Event handlers @@ -633,8 +638,11 @@ class Window(QtWidgets.QDialog): self.footer_button_play.setEnabled(False) self.footer_button_stop.setEnabled(False) - def on_suspend_clicked(self): - self._suspend_logs = not self._suspend_logs + def on_suspend_clicked(self, value=None): + self.apply_log_suspend_value(not self._suspend_logs) + + def apply_log_suspend_value(self, value): + self._suspend_logs = value if self.state["current_page"] == "terminal": self.on_tab_changed("overview") From 96b4c6e2519c2fed5cfc003f26eefb0530412a55 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 16:06:15 +0200 Subject: [PATCH 322/947] set PYBLISH_SUSPEND_LOGS by default if published family is editorial --- pype/modules/standalonepublish/publish.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/modules/standalonepublish/publish.py b/pype/modules/standalonepublish/publish.py index 0a30d5f2cb..27062e8457 100644 --- a/pype/modules/standalonepublish/publish.py +++ b/pype/modules/standalonepublish/publish.py @@ -83,6 +83,8 @@ def cli_publish(data, gui=True): envcopy["SAPUBLISH_INPATH"] = json_data_path envcopy["PYBLISHGUI"] = "pyblish_pype" envcopy["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) + if data.get("family", "").lower() == "editorial": + envcopy["PYBLISH_SUSPEND_LOGS"] = "1" result = execute( [sys.executable, PUBLISH_SCRIPT_PATH], From 86f51125fbec6d5bb040c32e739c3cae2d275f1b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 16:08:08 +0200 Subject: [PATCH 323/947] changed key __version__ in compositing json to __schema_version__ --- .../publish/extract_images_for_compositing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index 87d6aa13cb..46787e3ad1 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -84,7 +84,7 @@ class ExtractImagesForComp(pype.api.Extractor): def export_compositing_images(self, psd_object, output_dir, publish_dir): json_data = { - "__version__": 1, + "__schema_version__": 1, "children": [] } transfers = [] From b68a9c2542f7ebaf89ec254165955ff994ebd3ff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 16:50:03 +0200 Subject: [PATCH 324/947] fix qcss bug --- pype/modules/standalonepublish/widgets/widget_drop_empty.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_empty.py b/pype/modules/standalonepublish/widgets/widget_drop_empty.py index a68b91da59..d352e70355 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_empty.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_empty.py @@ -25,14 +25,14 @@ class DropEmpty(QtWidgets.QWidget): self._label = QtWidgets.QLabel('Drag & Drop') self._label.setFont(font) self._label.setStyleSheet( - 'background-color: rgb(255, 255, 255, 0);' + 'background-color: transparent;' ) font.setPointSize(12) self._sub_label = QtWidgets.QLabel('(drop files here)') self._sub_label.setFont(font) self._sub_label.setStyleSheet( - 'background-color: rgb(255, 255, 255, 0);' + 'background-color: transparent;' ) layout.addWidget(self._label, alignment=BottomCenterAlignment) From c8afe556b7df351754217d929433ea574965d597 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:05:38 +0200 Subject: [PATCH 325/947] added more groups and made are not case sensitive now --- .../publish/extract_images_for_compositing.py | 12 ++++++++++-- .../publish/extract_layout_images.py | 11 +++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index 46787e3ad1..48e09d4b93 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -15,7 +15,10 @@ class ExtractImagesForComp(pype.api.Extractor): new_instance_family = "image" # Presetable - allowed_group_names = ["OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide"] + 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 @@ -27,6 +30,11 @@ class ExtractImagesForComp(pype.api.Extractor): "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") @@ -91,7 +99,7 @@ class ExtractImagesForComp(pype.api.Extractor): for main_idx, main_layer in enumerate(psd_object): if ( not main_layer.is_visible() - or main_layer.name not in self.allowed_group_names + or main_layer.name.lower() not in self.allowed_group_names or not main_layer.is_group ): continue diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_layout_images.py index a69a08f0d8..d5359ca22e 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_layout_images.py @@ -18,7 +18,10 @@ class ExtractImageForLayout(pype.api.Extractor): ignored_instance_data_keys = ("name", "label", "stagingDir", "version") # Presetable - allowed_group_names = ["OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide"] + 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 @@ -30,6 +33,10 @@ class ExtractImageForLayout(pype.api.Extractor): "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.") @@ -57,7 +64,7 @@ class ExtractImageForLayout(pype.api.Extractor): for layer in psd_object: if ( not layer.is_visible() - or layer.name not in self.allowed_group_names + or layer.name.lower() not in self.allowed_group_names ): continue From d7031c616e6a7126fe317da71ba1c9929dadca9b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:15:47 +0200 Subject: [PATCH 326/947] renamed `ExtractImagesForComp` to `ExtractBGForComp` and used `background` family instead of `image` --- .../publish/extract_images_for_compositing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py index 48e09d4b93..7304a17319 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py @@ -7,12 +7,12 @@ from avalon import io PSDImage = None -class ExtractImagesForComp(pype.api.Extractor): - label = "Extract Images for Compositing" +class ExtractBGForComp(pype.api.Extractor): + label = "Extract Background for Compositing" families = ["imageForComp"] hosts = ["standalonepublisher"] - new_instance_family = "image" + new_instance_family = "background" # Presetable allowed_group_names = [ From 7e13ef31d8646f1071f46c8a20057d74944290dd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:16:26 +0200 Subject: [PATCH 327/947] renamed publish filename --- ...ct_images_for_compositing.py => extract_bg_for_compositing.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/plugins/standalonepublisher/publish/{extract_images_for_compositing.py => extract_bg_for_compositing.py} (100%) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py similarity index 100% rename from pype/plugins/standalonepublisher/publish/extract_images_for_compositing.py rename to pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py From 9fbac5f8ff43b9680d7a1c7b91a76d276c25315a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:20:43 +0200 Subject: [PATCH 328/947] renamed extract layout images to extract images from psd and disabled it --- ...xtract_layout_images.py => extract_images_from_psd.py} | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename pype/plugins/standalonepublisher/publish/{extract_layout_images.py => extract_images_from_psd.py} (96%) diff --git a/pype/plugins/standalonepublisher/publish/extract_layout_images.py b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py similarity index 96% rename from pype/plugins/standalonepublisher/publish/extract_layout_images.py rename to pype/plugins/standalonepublisher/publish/extract_images_from_psd.py index d5359ca22e..2ac6001755 100644 --- a/pype/plugins/standalonepublisher/publish/extract_layout_images.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py @@ -7,16 +7,18 @@ from avalon import io PSDImage = None -class ExtractImageForLayout(pype.api.Extractor): +class ExtractImagesFromPSD(pype.api.Extractor): + # PLUGIN is not currently enabled because was decided to use different + # approach + enabled = False - label = "Extract Images for Layout" + label = "Extract Images from PSD" order = pyblish.api.ExtractorOrder + 0.02 families = ["imageForLayout"] 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", From 3d44b1cd10d5c8215215d86d56678f76636bbea1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:25:39 +0200 Subject: [PATCH 329/947] created copy of previous plugin for layout exporting --- .../publish/extract_bg_main_groups.py | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py new file mode 100644 index 0000000000..fe4a2e360c --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -0,0 +1,163 @@ +import os +import copy +import pype.api +import pyblish.api +from avalon import io + +PSDImage = None + + +class ExtractBGMainGroups(pype.api.Extractor): + + label = "Extract Background Main Groups" + order = pyblish.api.ExtractorOrder + 0.02 + families = ["imageForLayout"] + hosts = ["standalonepublisher"] + + new_instance_family = "background" + + # 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 + 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 = 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 = io.find_one( + { + "type": "version", + "parent": subset_doc["_id"] + }, + sort=[("name", -1)] + ) + if version_doc: + return int(version_doc["name"]) + return None From 544f59db7f2d80a17e006d57cb845da3578382c8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:39:42 +0200 Subject: [PATCH 330/947] added checks for layers exporting --- .../publish/extract_bg_for_compositing.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index 7304a17319..889a3cead2 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -127,6 +127,14 @@ class ExtractBGForComp(pype.api.Extractor): } 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 + filename = "{:0>2}_{}_{:0>2}_{}.png".format( main_idx + 1, main_layer.name, layer_idx + 1, layer.name ) @@ -144,7 +152,8 @@ class ExtractBGForComp(pype.api.Extractor): main_layer_data["children"].append(layer_data) - json_data["children"].append(main_layer_data) + if main_layer_data["children"]: + json_data["children"].append(main_layer_data) return json_data, transfers From 69bae8f2ae1094b11f08a4de9330b3f9975c81cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:40:05 +0200 Subject: [PATCH 331/947] replace spaces with underscore in layer names for output filename --- .../publish/extract_bg_for_compositing.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index 889a3cead2..43d4a91964 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -135,8 +135,11 @@ class ExtractBGForComp(pype.api.Extractor): ).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 + main_idx + 1, main_layer_name, layer_idx + 1, layer_name ) layer_data = { "index": layer_idx, From f37da8d9314e6dc103f29f9135b6c7d179e7d577 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:41:28 +0200 Subject: [PATCH 332/947] modified extract bg main groups to have similir output to extract bg for compositing --- .../publish/extract_bg_for_compositing.py | 2 +- .../publish/extract_bg_main_groups.py | 192 +++++++++++------- 2 files changed, 122 insertions(+), 72 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index 43d4a91964..0c0eee7f83 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -46,7 +46,7 @@ class ExtractBGForComp(pype.api.Extractor): instance.data["transfers"] = [] for repre in tuple(repres): - # Skip all non files without .psd extension + # Skip all files without .psd extension if repre["ext"] != ".psd": continue diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py index fe4a2e360c..4920a92ba9 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -1,5 +1,6 @@ import os import copy +import json import pype.api import pyblish.api from avalon import io @@ -8,7 +9,6 @@ PSDImage = None class ExtractBGMainGroups(pype.api.Extractor): - label = "Extract Background Main Groups" order = pyblish.api.ExtractorOrder + 0.02 families = ["imageForLayout"] @@ -41,105 +41,155 @@ class ExtractBGMainGroups(pype.api.Extractor): 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"] = [] + for repre in tuple(repres): # Skip all files without .psd extension if repre["ext"] != ".psd": continue - # TODO add check of list of "files" value + # 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"] + + # 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) - self.create_new_instances(instance, psd_object) + 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) - # Remove the instance from context - instance.context.remove(instance) + instance.data["transfers"].extend(transfers) + instance.data["representations"].remove(repre) + instance.data["representations"].append(new_repre) - def create_new_instances(self, instance, psd_object): - asset_doc = instance.data["assetEntity"] - for layer in psd_object: + def export_compositing_images(self, psd_object, output_dir, publish_dir): + json_data = { + "__schema_version__": 1, + "children": [] + } + transfers = [] + 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 + 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." + "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) + filename = "{}.png".format(layer_name) + layer_data = { + "index": layer_idx, + "name": layer.name, + "filename": filename + } - new_instance.data["label"] = " ".join( - (new_instance.data["asset"], instance_name) - ) + output_filepath = os.path.join(output_dir, filename) + dst_filepath = os.path.join(publish_dir, filename) + transfers.append((output_filepath, dst_filepath)) - # 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] + json_data["children"].append(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 = io.find_one({ From b91695d12b32821802a605807f504746c5256106 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:47:17 +0200 Subject: [PATCH 333/947] collect matching asset is only for psd_batch --- .../standalonepublisher/publish/collect_matching_asset.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index 3f28b610e0..ef05131417 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -14,7 +14,7 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): label = "Collect Matching Asset to Instance" order = pyblish.api.CollectorOrder - 0.05 hosts = ["standalonepublisher"] - family = ["image"] + family = ["psd_batch"] def process(self, instance): source_file = os.path.basename(instance.data["source"]).lower() @@ -38,9 +38,7 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): f"Matching asset found: {pformat(matching_asset_doc)}" ) - # QUESTION exception was limited to "psd_batch" family since - # it is required for processing - elif instance.data["family"] == "psd_batch": + else: # TODO better error message raise AssertionError(( "Filename \"{}\" does not match" From 801fcd7aabc5fd8fe493402f02e9c30f11695490 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:47:40 +0200 Subject: [PATCH 334/947] extract images from psd is also not active by default --- .../standalonepublisher/publish/extract_images_from_psd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py index 2ac6001755..2e7c9acb15 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py @@ -11,7 +11,7 @@ class ExtractImagesFromPSD(pype.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 = ["imageForLayout"] From dfc6aebeb262e912b9a04c3884dc1ff175be0cfb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:49:01 +0200 Subject: [PATCH 335/947] do not add "image" automatically to psd instance --- .../plugins/standalonepublisher/publish/collect_psd_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index f7ea30e810..d70c3a638b 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -55,7 +55,6 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): new_instance.data["label"] = f"{instance_name}" new_instance.data["subset"] = subset_name - new_instance.data["families"].append("image") # fix anatomy data anatomy_data_new = copy.deepcopy(anatomy_data) From 2ea8ad50c76a74974853e87a8ae43992d807aab2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 17:52:58 +0200 Subject: [PATCH 336/947] do not set psd as thumbnail --- pype/plugins/standalonepublisher/publish/collect_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 28cbe043ce..0e2d7fe2a6 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -159,7 +159,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): if "psd" in component["name"]: instance.data["source"] = component["files"] - component["thumbnail"] = True self.log.debug("Adding image:psd_batch family") instance.data["representations"].append(component) From b850c0af5160a7a31306bd7104c2107160cbc9a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 18:19:30 +0200 Subject: [PATCH 337/947] integrate new should process background family --- pype/plugins/global/publish/integrate_new.py | 3 ++- .../publish/extract_bg_for_compositing.py | 10 +++++----- .../publish/extract_bg_main_groups.py | 16 ++++++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 1d50e24e86..1c1be09a0a 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -84,7 +84,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "action", "harmony.template", "harmony.palette", - "editorial" + "editorial", + "background" ] exclude_families = ["clip"] db_representation_context_keys = [ diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index 0c0eee7f83..7a1b86ef50 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -45,16 +45,16 @@ class ExtractBGForComp(pype.api.Extractor): 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 if repre["ext"] != ".psd": continue - # 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"] diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py index 4920a92ba9..2dc89785da 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -51,19 +51,19 @@ class ExtractBGMainGroups(pype.api.Extractor): 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 if repre["ext"] != ".psd": continue - # 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"] - # Prepare json filepath where extracted metadata are stored json_filename = "{}.json".format(instance.name) json_full_path = os.path.join(staging_dir, json_filename) From 0efc5c9c8706c170fa5ebed26c2ac4222e2a89c8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 12 Aug 2020 17:20:29 +0100 Subject: [PATCH 338/947] Properly containerize image plane loads. --- pype/plugins/maya/load/load_image_plane.py | 66 +++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index 653a8d4128..08f7c99156 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -1,4 +1,9 @@ +import pymel.core as pc +import maya.cmds as cmds + from avalon import api +from avalon.maya.pipeline import containerise +from avalon.maya import lib from Qt import QtWidgets @@ -12,10 +17,14 @@ class ImagePlaneLoader(api.Loader): color = "orange" def load(self, context, name, namespace, data): - import pymel.core as pc - new_nodes = [] image_plane_depth = 1000 + asset = context['asset']['name'] + namespace = namespace or lib.unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) # Getting camera from selection. selection = pc.ls(selection=True) @@ -80,6 +89,9 @@ class ImagePlaneLoader(api.Loader): # Ask user whether to use sequence or still image. if context["representation"]["name"] == "exr": + # Ensure OpenEXRLoader plugin is loaded. + pc.loadPlugin("OpenEXRLoader.mll", quiet=True) + reply = QtWidgets.QMessageBox.information( None, "Frame Hold.", @@ -93,11 +105,51 @@ class ImagePlaneLoader(api.Loader): ) image_plane_shape.frameExtension.set(start_frame) - # Ensure OpenEXRLoader plugin is loaded. - pc.loadPlugin("OpenEXRLoader.mll", quiet=True) - new_nodes.extend( - [image_plane_transform.name(), image_plane_shape.name()] + [ + image_plane_transform.longName().split("|")[-1], + image_plane_shape.longName().split("|")[-1] + ] ) - return new_nodes + for node in new_nodes: + pc.rename(node, "{}:{}".format(namespace, node)) + + return containerise( + name=name, + namespace=namespace, + nodes=new_nodes, + context=context, + loader=self.__class__.__name__ + ) + + def update(self, container, representation): + image_plane_shape = None + for node in pc.PyNode(container["objectName"]).members(): + if node.nodeType() == "imagePlane": + image_plane_shape = node + + assert image_plane_shape is not None, "Image plane not found." + + path = api.get_representation_path(representation) + image_plane_shape.imageName.set(path) + cmds.setAttr( + container["objectName"] + ".representation", + str(representation["_id"]), + type="string" + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass From 18100497a68eb6a52bab3fe58a86d6e718f79615 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 Aug 2020 18:53:25 +0200 Subject: [PATCH 339/947] prep for background loading --- pype/plugins/harmony/load/load_background.py | 348 ++++++++++++++++++ .../harmony/load/load_imagesequence00.py | 23 ++ 2 files changed, 371 insertions(+) create mode 100644 pype/plugins/harmony/load/load_background.py create mode 100644 pype/plugins/harmony/load/load_imagesequence00.py diff --git a/pype/plugins/harmony/load/load_background.py b/pype/plugins/harmony/load/load_background.py new file mode 100644 index 0000000000..1e305678e6 --- /dev/null +++ b/pype/plugins/harmony/load/load_background.py @@ -0,0 +1,348 @@ +import os +import uuid + +import clique + +from avalon import api, harmony +import pype.lib +import json + +copy_files = """function copyFile(srcFilename, dstFilename) +{ + var srcFile = new PermanentFile(srcFilename); + var dstFile = new PermanentFile(dstFilename); + srcFile.copy(dstFile); +} +""" + +import_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +var TGATransparencyMode = 0; //Premultiplied wih Black +var SGITransparencyMode = 0; //Premultiplied wih Black +var LayeredPSDTransparencyMode = 1; //Straight +var FlatPSDTransparencyMode = 2; //Premultiplied wih White + +function getUniqueColumnName( column_prefix ) +{ + var suffix = 0; + // finds if unique name for a column + var column_name = column_prefix; + while(suffix < 2000) + { + if(!column.type(column_name)) + break; + + suffix = suffix + 1; + column_name = column_prefix + "_" + suffix; + } + return column_name; +} + +function import_files(args) +{ + var root = args[0]; + var files = args[1]; + var name = args[2]; + var start_frame = args[3]; + + var vectorFormat = null; + var extension = null; + var filename = files[0]; + + var pos = filename.lastIndexOf("."); + if( pos < 0 ) + return null; + + extension = filename.substr(pos+1).toLowerCase(); + + if(extension == "jpeg") + extension = "jpg"; + if(extension == "tvg") + { + vectorFormat = "TVG" + extension ="SCAN"; // element.add() will use this. + } + + var elemId = element.add( + name, + "BW", + scene.numberOfUnitsZ(), + extension.toUpperCase(), + vectorFormat + ); + if (elemId == -1) + { + // hum, unknown file type most likely -- let's skip it. + return null; // no read to add. + } + + var uniqueColumnName = getUniqueColumnName(name); + column.add(uniqueColumnName , "DRAWING"); + column.setElementIdOfDrawing(uniqueColumnName, elemId); + + var read = node.add(root, name, "READ", 0, 0, 0); + var transparencyAttr = node.getAttr( + read, frame.current(), "READ_TRANSPARENCY" + ); + var opacityAttr = node.getAttr(read, frame.current(), "OPACITY"); + transparencyAttr.setValue(true); + opacityAttr.setValue(true); + + var alignmentAttr = node.getAttr(read, frame.current(), "ALIGNMENT_RULE"); + alignmentAttr.setValue("ASIS"); + + var transparencyModeAttr = node.getAttr( + read, frame.current(), "applyMatteToColor" + ); + if (extension == "png") + transparencyModeAttr.setValue(PNGTransparencyMode); + if (extension == "tga") + transparencyModeAttr.setValue(TGATransparencyMode); + if (extension == "sgi") + transparencyModeAttr.setValue(SGITransparencyMode); + if (extension == "psd") + transparencyModeAttr.setValue(FlatPSDTransparencyMode); + if (extension == "jpg") + transparencyModeAttr.setValue(LayeredPSDTransparencyMode); + + node.linkAttr(read, "DRAWING.ELEMENT", uniqueColumnName); + + if (files.length == 1) + { + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, 1, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, "1"); + copyFile(files[0], drawingFilePath); + // Expose the image for the entire frame range. + for( var i =0; i <= frame.numberOf() - 1; ++i) + { + timing = start_frame + i + column.setEntry(uniqueColumnName, 1, timing, "1"); + } + } else { + // Create a drawing for each file. + for( var i =0; i <= files.length - 1; ++i) + { + timing = start_frame + i + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, timing, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, timing.toString()); + copyFile( files[i], drawingFilePath ); + + column.setEntry(uniqueColumnName, 1, timing, timing.toString()); + } + } + + var green_color = new ColorRGBA(0, 255, 0, 255); + node.setColor(read, green_color); + + return read; +} +import_files +""" + +replace_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +var TGATransparencyMode = 0; //Premultiplied wih Black +var SGITransparencyMode = 0; //Premultiplied wih Black +var LayeredPSDTransparencyMode = 1; //Straight +var FlatPSDTransparencyMode = 2; //Premultiplied wih White + +function replace_files(args) +{ + var files = args[0]; + MessageLog.trace(files); + MessageLog.trace(files.length); + var _node = args[1]; + var start_frame = args[2]; + + var _column = node.linkedColumn(_node, "DRAWING.ELEMENT"); + var elemId = column.getElementIdOfDrawing(_column); + + // Delete existing drawings. + var timings = column.getDrawingTimings(_column); + for( var i =0; i <= timings.length - 1; ++i) + { + column.deleteDrawingAt(_column, parseInt(timings[i])); + } + + + var filename = files[0]; + var pos = filename.lastIndexOf("."); + if( pos < 0 ) + return null; + var extension = filename.substr(pos+1).toLowerCase(); + + if(extension == "jpeg") + extension = "jpg"; + + var transparencyModeAttr = node.getAttr( + _node, frame.current(), "applyMatteToColor" + ); + if (extension == "png") + transparencyModeAttr.setValue(PNGTransparencyMode); + if (extension == "tga") + transparencyModeAttr.setValue(TGATransparencyMode); + if (extension == "sgi") + transparencyModeAttr.setValue(SGITransparencyMode); + if (extension == "psd") + transparencyModeAttr.setValue(FlatPSDTransparencyMode); + if (extension == "jpg") + transparencyModeAttr.setValue(LayeredPSDTransparencyMode); + + if (files.length == 1) + { + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, 1, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, "1"); + copyFile(files[0], drawingFilePath); + MessageLog.trace(files[0]); + MessageLog.trace(drawingFilePath); + // Expose the image for the entire frame range. + for( var i =0; i <= frame.numberOf() - 1; ++i) + { + timing = start_frame + i + column.setEntry(_column, 1, timing, "1"); + } + } else { + // Create a drawing for each file. + for( var i =0; i <= files.length - 1; ++i) + { + timing = start_frame + i + // Create a drawing drawing, 'true' indicate that the file exists. + Drawing.create(elemId, timing, true); + // Get the actual path, in tmp folder. + var drawingFilePath = Drawing.filename(elemId, timing.toString()); + copyFile( files[i], drawingFilePath ); + + column.setEntry(_column, 1, timing, timing.toString()); + } + } + + var green_color = new ColorRGBA(0, 255, 0, 255); + node.setColor(_node, green_color); +} +replace_files +""" + + +class BackgroundLoader(api.Loader): + """Load images + Stores the imported asset in a container named after the asset. + """ + families = ["background", "image"] + representations = ["json"] + + def load(self, context, name=None, namespace=None, data=None): + + with open(file) as json_file: + data = json.load(json_file) + + layers = list() + + for child in data['children']: + if child.get("filename"): + print(child["filename"]) + layers.append(child["filename"]) + else: + for layer in child['children']: + if layer.get("filename"): + print(layer["filename"]) + layers.append(layer["filename"]) + + print(layers) + + bg_folder = os.path.dirname(self.fname).replace("\\", "/") + + subset_name = context["subset"]["name"] + subset_name += "_{}".format(uuid.uuid4()) + + for layer in sorted(sorted): + file_to_import = os.path.join(bg_folder, layer) + + read_node = harmony.send( + { + "function": copy_files + import_files, + "args": ["Top", file_to_import, layer, 1] + } + )["result"] + + + return harmony.containerise( + subset_name, + namespace, + subset_name, + context, + self.__class__.__name__ + ) + + def update(self, container, representation): + node = harmony.find_node_by_name(container["name"], "READ") + + path = api.get_representation_path(representation) + collections, remainder = clique.assemble( + os.listdir(os.path.dirname(path)) + ) + files = [] + if collections: + for f in list(collections[0]): + files.append( + os.path.join( + os.path.dirname(path), f + ).replace("\\", "/") + ) + else: + files.append( + os.path.join( + os.path.dirname(path), remainder[0] + ).replace("\\", "/") + ) + + harmony.send( + { + "function": copy_files + replace_files, + "args": [files, node, 1] + } + ) + + # Colour node. + func = """function func(args){ + for( var i =0; i <= args[0].length - 1; ++i) + { + var red_color = new ColorRGBA(255, 0, 0, 255); + var green_color = new ColorRGBA(0, 255, 0, 255); + if (args[1] == "red"){ + node.setColor(args[0], red_color); + } + if (args[1] == "green"){ + node.setColor(args[0], green_color); + } + } + } + func + """ + if pype.lib.is_latest(representation): + harmony.send({"function": func, "args": [node, "green"]}) + else: + harmony.send({"function": func, "args": [node, "red"]}) + + harmony.imprint( + node, {"representation": str(representation["_id"])} + ) + + def remove(self, container): + node = harmony.find_node_by_name(container["name"], "READ") + + func = """function deleteNode(_node) + { + node.deleteNode(_node, true, true); + } + deleteNode + """ + harmony.send( + {"function": func, "args": [node]} + ) + + def switch(self, container, representation): + self.update(container, representation) diff --git a/pype/plugins/harmony/load/load_imagesequence00.py b/pype/plugins/harmony/load/load_imagesequence00.py new file mode 100644 index 0000000000..82a33678ce --- /dev/null +++ b/pype/plugins/harmony/load/load_imagesequence00.py @@ -0,0 +1,23 @@ +import os + +import json + +file = r"G:\My Drive\pypeRoot\milo_s01\episodes\ml102\ml102_shots\ml102_sh0850\publish\image\imageForComp\v001\ml_ml102_sh0850_imageForComp_v001.json" + +with open(file) as json_file: + data = json.load(json_file) + +layers = list() + +for child in data['children']: + if child.get("filename"): + print(child["filename"]) + layers.append(child["filename"]) + else: + for layer in child['children']: + if layer.get("filename"): + print(layer["filename"]) + layers.append(layer["filename"]) + +for layer in sorted(layers): + print(layer) From 4aa995cfe7fb261402bba523d50dfd822281c1c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 19:48:15 +0200 Subject: [PATCH 340/947] Other order is not int but None --- pype/tools/pyblish_pype/model.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index b7b300f154..fdcdffd33f 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -440,9 +440,6 @@ class PluginModel(QtGui.QStandardItemModel): if label is None: label = "Other" - if order is None: - order = 99999999999999 - group_item = self.group_items.get(label) if not group_item: group_item = GroupItem(label, order=order) From 0c1b37af823d01af459ce2f2860e07c0f5f489f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 19:48:38 +0200 Subject: [PATCH 341/947] passed group checks order equality instead of comparison --- pype/tools/pyblish_pype/window.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 7d79e0e26c..a1e023bb0a 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -771,10 +771,10 @@ class Window(QtWidgets.QDialog): for group_item in self.plugin_model.group_items.values(): # TODO check only plugins from the group - if ( - group_item.publish_states & GroupStates.HasFinished - or (order is not None and group_item.order >= order) - ): + if group_item.publish_states & GroupStates.HasFinished: + continue + + if order != group_item.order: continue if group_item.publish_states & GroupStates.HasError: From 35731d94d8210ab9ae8a32f4cd98220f37f45213 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 19:49:03 +0200 Subject: [PATCH 342/947] controller sends group order which was processed instead of which will be processed --- pype/tools/pyblish_pype/control.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index 77badf71b6..0162848f2b 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -250,6 +250,8 @@ class Controller(QtCore.QObject): self.processing["current_group_order"] is not None and plugin.order > self.processing["current_group_order"] ): + current_group_order = self.processing["current_group_order"] + new_next_group_order = None new_current_group_order = self.processing["next_group_order"] if new_current_group_order is not None: @@ -270,12 +272,13 @@ class Controller(QtCore.QObject): if self.collect_state == 0: self.collect_state = 1 self.switch_toggleability.emit(True) - self.passed_group.emit(new_current_group_order) + self.passed_group.emit(current_group_order) yield IterationBreak("Collected") - self.passed_group.emit(new_current_group_order) - if self.errored: - yield IterationBreak("Last group errored") + else: + self.passed_group.emit(current_group_order) + if self.errored: + yield IterationBreak("Last group errored") if self.collect_state == 1: self.collect_state = 2 From 40accd1c391639a6f1a73fd125af0f21816755fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:22:54 +0200 Subject: [PATCH 343/947] Make sure tools and applications attributes always have at least one item in to not raise an exception --- pype/modules/ftrack/actions/action_create_cust_attrs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 0c7e311377..21c4743725 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -409,6 +409,10 @@ class CustomAttributes(BaseAction): )) ) ) + + # Make sure there is at least one item + if not app_definitions: + app_definitions.append({"empty": "< Empty >"}) return app_definitions def applications_attribute(self, event): @@ -432,6 +436,10 @@ class CustomAttributes(BaseAction): if usage: tools_data.append({tool_name: tool_name}) + # Make sure there is at least one item + if not tools_data: + tools_data.append({"empty": "< Empty >"}) + tools_custom_attr_data = { "label": "Tools", "key": "tools_env", From 18a1a5d6798dc0bf228a159bf3c299b0adce9c02 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:44:52 +0200 Subject: [PATCH 344/947] fix project check --- pype/tools/launcher/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index ae29c65297..7ad161236f 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -124,7 +124,7 @@ class ActionModel(QtGui.QStandardItemModel): def discover(self): """Set up Actions cache. Run this for each new project.""" - if io.Session.get("AVALON_PROJECT") is None: + if not io.Session.get("AVALON_PROJECT"): self._registered_actions = list() return From 6ea3c492d160cd80b98cd69feb0337d01cd573d5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:45:07 +0200 Subject: [PATCH 345/947] stylesheet in code was moved to css --- pype/tools/launcher/app.py | 46 +++++++++++----------------------- pype/tools/launcher/widgets.py | 7 +++--- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index 9cef313bf5..abc350641b 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -34,11 +34,17 @@ class IconListView(QtWidgets.QListView): # Workaround for scrolling being super slow or fast when # toggling between the two visual modes self.setVerticalScrollMode(self.ScrollPerPixel) + self.setObjectName("IconView") - self._mode = 0 + self._mode = None self.set_mode(mode) def set_mode(self, mode): + if mode == self._mode: + return + + self._mode = mode + if mode == self.IconMode: self.setViewMode(QtWidgets.QListView.IconMode) self.setResizeMode(QtWidgets.QListView.Adjust) @@ -49,31 +55,15 @@ class IconListView(QtWidgets.QListView): self.setSpacing(0) self.setAlternatingRowColors(False) - self.setStyleSheet(""" - QListView { - font-size: 11px; - border: 0px; - padding: 0px; - margin: 0px; - - } - - QListView::item { - margin-top: 6px; - /* Won't work without borders set */ - border: 0px; - } - - /* For icon only */ - QListView::icon { - top: 3px; - } - """) + self.setProperty("mode", "icon") + self.style().polish(self) self.verticalScrollBar().setSingleStep(30) elif self.ListMode: - self.setStyleSheet("") # clear stylesheet + self.setProperty("mode", "list") + self.style().polish(self) + self.setViewMode(QtWidgets.QListView.ListMode) self.setResizeMode(QtWidgets.QListView.Adjust) self.setWrapping(False) @@ -85,8 +75,6 @@ class IconListView(QtWidgets.QListView): self.verticalScrollBar().setSingleStep(33.33) - self._mode = mode - def mousePressEvent(self, event): if event.button() == QtCore.Qt.RightButton: self.set_mode(int(not self._mode)) @@ -145,7 +133,6 @@ class AssetsPanel(QtWidgets.QWidget): back.setFixedWidth(23) back.setFixedHeight(23) projects = ProjectBar() - projects.layout().setContentsMargins(0, 0, 0, 0) layout.addWidget(back) layout.addWidget(projects) @@ -216,10 +203,8 @@ class AssetsPanel(QtWidgets.QWidget): channel="assets") def on_project_changed(self): - - project = self.data["model"]["projects"].get_current_project() - - api.Session["AVALON_PROJECT"] = project + project_name = self.data["model"]["projects"].get_current_project() + api.Session["AVALON_PROJECT"] = project_name self.data["model"]["assets"].refresh() # Force asset change callback to ensure tasks are correctly reset @@ -392,8 +377,7 @@ class Window(QtWidgets.QDialog): io.Session["AVALON_PROJECT"] = project_name # Update the Action plug-ins available for the current project - actions_model = self.data["model"]["actions"].model - actions_model.discover() + self.data["model"]["actions"].model.discover() def on_session_changed(self): self.refresh_actions() diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index c48376ae91..f8f4e17691 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -13,8 +13,6 @@ class ProjectBar(QtWidgets.QWidget): def __init__(self, parent=None): super(ProjectBar, self).__init__(parent) - layout = QtWidgets.QHBoxLayout(self) - self.model = ProjectModel() self.model.hide_invisible = True @@ -22,6 +20,8 @@ class ProjectBar(QtWidgets.QWidget): self.view.setModel(self.model) self.view.setRootModelIndex(QtCore.QModelIndex()) + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.view) self.setSizePolicy( @@ -119,7 +119,8 @@ class ActionBar(QtWidgets.QWidget): layout.setContentsMargins(8, 0, 8, 0) view = QtWidgets.QListView(self) - view.setObjectName("ActionView") + view.setProperty("mode", "icon") + view.setObjectName("IconView") view.setViewMode(QtWidgets.QListView.IconMode) view.setResizeMode(QtWidgets.QListView.Adjust) view.setSelectionMode(QtWidgets.QListView.NoSelection) From 22a0c4528e90fe7379c3f48cb1519a94ae7faec6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:49:38 +0200 Subject: [PATCH 346/947] ProjectBar made clear in variable names --- pype/tools/launcher/widgets.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index f8f4e17691..f87b16ecc6 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -16,13 +16,13 @@ class ProjectBar(QtWidgets.QWidget): self.model = ProjectModel() self.model.hide_invisible = True - self.view = QtWidgets.QComboBox() - self.view.setModel(self.model) - self.view.setRootModelIndex(QtCore.QModelIndex()) + self.project_combobox = QtWidgets.QComboBox() + self.project_combobox.setModel(self.model) + self.project_combobox.setRootModelIndex(QtCore.QModelIndex()) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.view) + layout.addWidget(self.project_combobox) self.setSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, @@ -33,7 +33,7 @@ class ProjectBar(QtWidgets.QWidget): self.refresh() # Signals - self.view.currentIndexChanged.connect(self.project_changed) + self.project_combobox.currentIndexChanged.connect(self.project_changed) # Set current project by default if it's set. project_name = api.Session.get("AVALON_PROJECT") @@ -41,25 +41,25 @@ class ProjectBar(QtWidgets.QWidget): self.set_project(project_name) def get_current_project(self): - return self.view.currentText() + return self.project_combobox.currentText() def set_project(self, project_name): - index = self.view.findText(project_name) + index = self.project_combobox.findText(project_name) if index >= 0: - self.view.setCurrentIndex(index) + self.project_combobox.setCurrentIndex(index) def refresh(self): prev_project_name = self.get_current_project() # Refresh without signals - self.view.blockSignals(True) - self.model.refresh() + self.project_combobox.blockSignals(True) + self.model.refresh() self.set_project(prev_project_name) - self.view.blockSignals(False) + self.project_combobox.blockSignals(False) - self.project_changed.emit(self.view.currentIndex()) + self.project_changed.emit(self.project_combobox.currentIndex()) class ActionDelegate(QtWidgets.QStyledItemDelegate): From 8f547489da0ef31790c774205b2c93911b6cec90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:50:00 +0200 Subject: [PATCH 347/947] moved ActionDelegate to delegates.py --- pype/tools/launcher/delegates.py | 46 ++++++++++++++++++++++++++++++++ pype/tools/launcher/widgets.py | 46 +------------------------------- 2 files changed, 47 insertions(+), 45 deletions(-) create mode 100644 pype/tools/launcher/delegates.py diff --git a/pype/tools/launcher/delegates.py b/pype/tools/launcher/delegates.py new file mode 100644 index 0000000000..750301cec4 --- /dev/null +++ b/pype/tools/launcher/delegates.py @@ -0,0 +1,46 @@ +from Qt import QtCore, QtWidgets, QtGui + + +class ActionDelegate(QtWidgets.QStyledItemDelegate): + extender_lines = 2 + extender_bg_brush = QtGui.QBrush(QtGui.QColor(100, 100, 100))#, 160)) + extender_fg = QtGui.QColor(255, 255, 255)#, 160) + + def __init__(self, group_role, *args, **kwargs): + super(ActionDelegate, self).__init__(*args, **kwargs) + self.group_role = group_role + + def paint(self, painter, option, index): + super(ActionDelegate, self).paint(painter, option, index) + is_group = index.data(self.group_role) + if not is_group: + return + + extender_width = int(option.decorationSize.width() / 2) + extender_height = int(option.decorationSize.height() / 2) + + exteder_rect = QtCore.QRectF( + option.rect.x() + (option.rect.width() / 10), + option.rect.y() + (option.rect.height() / 10), + extender_width, + extender_height + ) + path = QtGui.QPainterPath() + path.addRoundedRect(exteder_rect, 2, 2) + + painter.fillPath(path, self.extender_bg_brush) + + painter.setPen(self.extender_fg) + painter.drawPath(path) + + divider = (2 * self.extender_lines) + 1 + line_height = extender_height / divider + line_width = extender_width - (extender_width / 5) + pos_x = exteder_rect.x() + extender_width / 10 + pos_y = exteder_rect.y() + line_height + for _ in range(self.extender_lines): + line_rect = QtCore.QRectF( + pos_x, pos_y, line_width, round(line_height) + ) + painter.fillRect(line_rect, self.extender_fg) + pos_y += 2 * line_height diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index f87b16ecc6..a32548154b 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -3,6 +3,7 @@ from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome from avalon import api +from .delegates import ActionDelegate from .models import TaskModel, ActionModel, ProjectModel from .flickcharm import FlickCharm @@ -62,51 +63,6 @@ class ProjectBar(QtWidgets.QWidget): self.project_changed.emit(self.project_combobox.currentIndex()) -class ActionDelegate(QtWidgets.QStyledItemDelegate): - extender_lines = 2 - extender_bg_brush = QtGui.QBrush(QtGui.QColor(100, 100, 100))#, 160)) - extender_fg = QtGui.QColor(255, 255, 255)#, 160) - - def __init__(self, group_role, *args, **kwargs): - super(ActionDelegate, self).__init__(*args, **kwargs) - self.group_role = group_role - - def paint(self, painter, option, index): - super(ActionDelegate, self).paint(painter, option, index) - is_group = index.data(self.group_role) - if not is_group: - return - - extender_width = int(option.decorationSize.width() / 2) - extender_height = int(option.decorationSize.height() / 2) - - exteder_rect = QtCore.QRectF( - option.rect.x() + (option.rect.width() / 10), - option.rect.y() + (option.rect.height() / 10), - extender_width, - extender_height - ) - path = QtGui.QPainterPath() - path.addRoundedRect(exteder_rect, 2, 2) - - painter.fillPath(path, self.extender_bg_brush) - - painter.setPen(self.extender_fg) - painter.drawPath(path) - - divider = (2 * self.extender_lines) + 1 - line_height = extender_height / divider - line_width = extender_width - (extender_width / 5) - pos_x = exteder_rect.x() + extender_width / 10 - pos_y = exteder_rect.y() + line_height - for _ in range(self.extender_lines): - line_rect = QtCore.QRectF( - pos_x, pos_y, line_width, round(line_height) - ) - painter.fillRect(line_rect, self.extender_fg) - pos_y += 2 * line_height - - class ActionBar(QtWidgets.QWidget): """Launcher interface""" From 0b83ea44e14be6ad21a66eaf0d71645b3f07de12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:52:40 +0200 Subject: [PATCH 348/947] cleared action bar --- pype/tools/launcher/widgets.py | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index a32548154b..dedc3a5f50 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -109,25 +109,27 @@ class ActionBar(QtWidgets.QWidget): self.setMinimumHeight(rows * 75) def on_clicked(self, index): - if index.isValid(): - is_group = action = index.data(self.model.GROUP_ROLE) - if not is_group: - action = index.data(self.model.ACTION_ROLE) - self.action_clicked.emit(action) - return + if not index.isValid(): + return - menu = QtWidgets.QMenu(self) - actions = index.data(self.model.ACTION_ROLE) - actions_mapping = {} - for action in actions: - menu_action = QtWidgets.QAction(action.label or action.name) - menu.addAction(menu_action) - actions_mapping[menu_action] = action + is_group = index.data(self.model.GROUP_ROLE) + if not is_group: + action = index.data(self.model.ACTION_ROLE) + self.action_clicked.emit(action) + return - result = menu.exec_(QtGui.QCursor.pos()) - if result: - action = actions_mapping[result] - self.action_clicked.emit(action) + menu = QtWidgets.QMenu(self) + actions = index.data(self.model.ACTION_ROLE) + actions_mapping = {} + for action in actions: + menu_action = QtWidgets.QAction(action.label or action.name) + menu.addAction(menu_action) + actions_mapping[menu_action] = action + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + action = actions_mapping[result] + self.action_clicked.emit(action) class TasksWidget(QtWidgets.QWidget): From ad02f48d29103c3810271e668c2ab4d1c1c74963 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 21:56:54 +0200 Subject: [PATCH 349/947] made clear variable names --- pype/tools/launcher/widgets.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index dedc3a5f50..37c636423f 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -333,16 +333,20 @@ class SlidePageWidget(QtWidgets.QStackedWidget): } def slide_view(self, index, direction="right"): - if self.currentIndex() == index: return - offset = self.directions.get(direction) - assert offset is not None, "invalid slide direction: %s" % (direction,) + offset_direction = self.directions.get(direction) + if offset_direction is None: + print("BUG: invalid slide direction: {}".format(direction)) + return width = self.frameRect().width() height = self.frameRect().height() - offset = QtCore.QPoint(offset.x() * width, offset.y() * height) + offset = QtCore.QPoint( + offset_direction.x() * width, + offset_direction.y() * height + ) new_page = self.widget(index) new_page.setGeometry(0, 0, width, height) From 94b5b86caec850c90e362ffe20585ed3fee503fa Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 Aug 2020 22:03:13 +0200 Subject: [PATCH 350/947] .x/feature/maya_image_plane_containerize: --- pype/plugins/harmony/load/load_background.py | 151 +++++++++++------- .../harmony/load/load_imagesequence.py | 4 +- 2 files changed, 92 insertions(+), 63 deletions(-) diff --git a/pype/plugins/harmony/load/load_background.py b/pype/plugins/harmony/load/load_background.py index 1e305678e6..f8de1e1145 100644 --- a/pype/plugins/harmony/load/load_background.py +++ b/pype/plugins/harmony/load/load_background.py @@ -15,7 +15,7 @@ copy_files = """function copyFile(srcFilename, dstFilename) } """ -import_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +import_files = """var PNGTransparencyMode = 1; //Premultiplied wih Black var TGATransparencyMode = 0; //Premultiplied wih Black var SGITransparencyMode = 0; //Premultiplied wih Black var LayeredPSDTransparencyMode = 1; //Straight @@ -142,7 +142,7 @@ function import_files(args) import_files """ -replace_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +replace_files = """var PNGTransparencyMode = 1; //Premultiplied wih Black var TGATransparencyMode = 0; //Premultiplied wih Black var SGITransparencyMode = 0; //Premultiplied wih Black var LayeredPSDTransparencyMode = 1; //Straight @@ -231,12 +231,12 @@ class BackgroundLoader(api.Loader): """Load images Stores the imported asset in a container named after the asset. """ - families = ["background", "image"] + families = ["background"] representations = ["json"] def load(self, context, name=None, namespace=None, data=None): - with open(file) as json_file: + with open(self.fname) as json_file: data = json.load(json_file) layers = list() @@ -252,14 +252,18 @@ class BackgroundLoader(api.Loader): layers.append(layer["filename"]) print(layers) + print(data) - bg_folder = os.path.dirname(self.fname).replace("\\", "/") + bg_folder = os.path.dirname(self.fname) subset_name = context["subset"]["name"] - subset_name += "_{}".format(uuid.uuid4()) + # read_node_name += "_{}".format(uuid.uuid4()) + container_nodes = [] - for layer in sorted(sorted): - file_to_import = os.path.join(bg_folder, layer) + for layer in sorted(layers): + file_to_import = [os.path.join(bg_folder, layer).replace("\\", "/")] + + print(f"FILE TO IMPORT: {file_to_import}") read_node = harmony.send( { @@ -267,6 +271,7 @@ class BackgroundLoader(api.Loader): "args": ["Top", file_to_import, layer, 1] } )["result"] + container_nodes.append(read_node) return harmony.containerise( @@ -274,75 +279,99 @@ class BackgroundLoader(api.Loader): namespace, subset_name, context, - self.__class__.__name__ + self.__class__.__name__, + nodes=container_nodes ) def update(self, container, representation): - node = harmony.find_node_by_name(container["name"], "READ") path = api.get_representation_path(representation) - collections, remainder = clique.assemble( - os.listdir(os.path.dirname(path)) - ) - files = [] - if collections: - for f in list(collections[0]): - files.append( - os.path.join( - os.path.dirname(path), f - ).replace("\\", "/") + + with open(path) as json_file: + data = json.load(json_file) + + layers = list() + + for child in data['children']: + if child.get("filename"): + print(child["filename"]) + layers.append(child["filename"]) + else: + for layer in child['children']: + if layer.get("filename"): + print(layer["filename"]) + layers.append(layer["filename"]) + + bg_folder = os.path.dirname(path) + + path = api.get_representation_path(representation) + + print(container) + + for layer in sorted(layers): + file_to_import = [os.path.join(bg_folder, layer).replace("\\", "/")] + print(20*"#") + print(f"FILE TO REPLACE: {file_to_import}") + print(f"LAYER: {layer}") + node = harmony.find_node_by_name(layer, "READ") + print(f"{node}") + + if node in container['nodes']: + harmony.send( + { + "function": copy_files + replace_files, + "args": [file_to_import, node, 1] + } ) - else: - files.append( - os.path.join( - os.path.dirname(path), remainder[0] - ).replace("\\", "/") - ) + else: + read_node = harmony.send( + { + "function": copy_files + import_files, + "args": ["Top", file_to_import, layer, 1] + } + )["result"] + container['nodes'].append(read_node) - harmony.send( - { - "function": copy_files + replace_files, - "args": [files, node, 1] - } - ) - # Colour node. - func = """function func(args){ - for( var i =0; i <= args[0].length - 1; ++i) - { - var red_color = new ColorRGBA(255, 0, 0, 255); - var green_color = new ColorRGBA(0, 255, 0, 255); - if (args[1] == "red"){ - node.setColor(args[0], red_color); - } - if (args[1] == "green"){ - node.setColor(args[0], green_color); + # Colour node. + func = """function func(args){ + for( var i =0; i <= args[0].length - 1; ++i) + { + var red_color = new ColorRGBA(255, 0, 0, 255); + var green_color = new ColorRGBA(0, 255, 0, 255); + if (args[1] == "red"){ + node.setColor(args[0], red_color); + } + if (args[1] == "green"){ + node.setColor(args[0], green_color); + } } } - } - func - """ - if pype.lib.is_latest(representation): - harmony.send({"function": func, "args": [node, "green"]}) - else: - harmony.send({"function": func, "args": [node, "red"]}) + func + """ + if pype.lib.is_latest(representation): + harmony.send({"function": func, "args": [node, "green"]}) + else: + harmony.send({"function": func, "args": [node, "red"]}) harmony.imprint( - node, {"representation": str(representation["_id"])} + container['name'], {"representation": str(representation["_id"]), + "nodes": container['nodes']} ) def remove(self, container): - node = harmony.find_node_by_name(container["name"], "READ") + for node in container.get("nodes"): - func = """function deleteNode(_node) - { - node.deleteNode(_node, true, true); - } - deleteNode - """ - harmony.send( - {"function": func, "args": [node]} - ) + func = """function deleteNode(_node) + { + node.deleteNode(_node, true, true); + } + deleteNode + """ + harmony.send( + {"function": func, "args": [node]} + ) + harmony.imprint(container['name'], {}, remove=True) def switch(self, container, representation): self.update(container, representation) diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index f81018d0fb..0bc7c07ce5 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -14,7 +14,7 @@ copy_files = """function copyFile(srcFilename, dstFilename) } """ -import_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +import_files = """var PNGTransparencyMode = 1; //Premultiplied wih Black var TGATransparencyMode = 0; //Premultiplied wih Black var SGITransparencyMode = 0; //Premultiplied wih Black var LayeredPSDTransparencyMode = 1; //Straight @@ -141,7 +141,7 @@ function import_files(args) import_files """ -replace_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black +replace_files = """var PNGTransparencyMode = 1; //Premultiplied wih Black var TGATransparencyMode = 0; //Premultiplied wih Black var SGITransparencyMode = 0; //Premultiplied wih Black var LayeredPSDTransparencyMode = 1; //Straight From 7cee74677dfbd66afb99673e672d02b7a7f37a87 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 Aug 2020 22:20:25 +0200 Subject: [PATCH 351/947] containerise all loaders --- pype/plugins/harmony/load/load_audio.py | 16 +++++++++++++ pype/plugins/harmony/load/load_background.py | 8 ------- .../harmony/load/load_imagesequence.py | 6 +++-- .../harmony/load/load_imagesequence00.py | 23 ------------------- .../harmony/load/load_template_workfile.py | 16 +++++++++++++ 5 files changed, 36 insertions(+), 33 deletions(-) delete mode 100644 pype/plugins/harmony/load/load_imagesequence00.py diff --git a/pype/plugins/harmony/load/load_audio.py b/pype/plugins/harmony/load/load_audio.py index a17af78964..694fda3247 100644 --- a/pype/plugins/harmony/load/load_audio.py +++ b/pype/plugins/harmony/load/load_audio.py @@ -40,3 +40,19 @@ class ImportAudioLoader(api.Loader): harmony.send( {"function": func, "args": [context["subset"]["name"], wav_file]} ) + + subset_name = context["subset"]["name"] + + return harmony.containerise( + subset_name, + namespace, + subset_name, + context, + self.__class__.__name__ + ) + + def update(self, container, representation): + pass + + def remove(self, container): + pass diff --git a/pype/plugins/harmony/load/load_background.py b/pype/plugins/harmony/load/load_background.py index f8de1e1145..f96fc275be 100644 --- a/pype/plugins/harmony/load/load_background.py +++ b/pype/plugins/harmony/load/load_background.py @@ -243,17 +243,12 @@ class BackgroundLoader(api.Loader): for child in data['children']: if child.get("filename"): - print(child["filename"]) layers.append(child["filename"]) else: for layer in child['children']: if layer.get("filename"): - print(layer["filename"]) layers.append(layer["filename"]) - print(layers) - print(data) - bg_folder = os.path.dirname(self.fname) subset_name = context["subset"]["name"] @@ -263,8 +258,6 @@ class BackgroundLoader(api.Loader): for layer in sorted(layers): file_to_import = [os.path.join(bg_folder, layer).replace("\\", "/")] - print(f"FILE TO IMPORT: {file_to_import}") - read_node = harmony.send( { "function": copy_files + import_files, @@ -273,7 +266,6 @@ class BackgroundLoader(api.Loader): )["result"] container_nodes.append(read_node) - return harmony.containerise( subset_name, namespace, diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index 0bc7c07ce5..774782b092 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -230,7 +230,7 @@ class ImageSequenceLoader(api.Loader): """Load images Stores the imported asset in a container named after the asset. """ - families = ["shot", "render", "image"] + families = ["shot", "render", "image", "plate"] representations = ["jpeg", "png", "jpg"] def load(self, context, name=None, namespace=None, data=None): @@ -267,7 +267,8 @@ class ImageSequenceLoader(api.Loader): namespace, read_node, context, - self.__class__.__name__ + self.__class__.__name__, + nodes=[read_node] ) def update(self, container, representation): @@ -336,6 +337,7 @@ class ImageSequenceLoader(api.Loader): harmony.send( {"function": func, "args": [node]} ) + harmony.imprint(node, {}, remove=True) def switch(self, container, representation): self.update(container, representation) diff --git a/pype/plugins/harmony/load/load_imagesequence00.py b/pype/plugins/harmony/load/load_imagesequence00.py deleted file mode 100644 index 82a33678ce..0000000000 --- a/pype/plugins/harmony/load/load_imagesequence00.py +++ /dev/null @@ -1,23 +0,0 @@ -import os - -import json - -file = r"G:\My Drive\pypeRoot\milo_s01\episodes\ml102\ml102_shots\ml102_sh0850\publish\image\imageForComp\v001\ml_ml102_sh0850_imageForComp_v001.json" - -with open(file) as json_file: - data = json.load(json_file) - -layers = list() - -for child in data['children']: - if child.get("filename"): - print(child["filename"]) - layers.append(child["filename"]) - else: - for layer in child['children']: - if layer.get("filename"): - print(layer["filename"]) - layers.append(layer["filename"]) - -for layer in sorted(layers): - print(layer) diff --git a/pype/plugins/harmony/load/load_template_workfile.py b/pype/plugins/harmony/load/load_template_workfile.py index db67f20ff7..3e79cc1903 100644 --- a/pype/plugins/harmony/load/load_template_workfile.py +++ b/pype/plugins/harmony/load/load_template_workfile.py @@ -35,6 +35,22 @@ class ImportTemplateLoader(api.Loader): shutil.rmtree(temp_dir) + subset_name = context["subset"]["name"] + + return harmony.containerise( + subset_name, + namespace, + subset_name, + context, + self.__class__.__name__ + ) + + def update(self, container, representation): + pass + + def remove(self, container): + pass + class ImportWorkfileLoader(ImportTemplateLoader): """Import workfiles.""" From 40ade937d42677ac233ee03827b7ee8aa6110f22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 22:23:03 +0200 Subject: [PATCH 352/947] organization changes --- pype/tools/launcher/widgets.py | 43 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 37c636423f..a264466dbc 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -237,11 +237,8 @@ class ActionHistory(QtWidgets.QPushButton): if not self._history: return - point = QtGui.QCursor().pos() - widget = QtWidgets.QListWidget() widget.setSelectionMode(widget.NoSelection) - widget.setStyleSheet(""" * { font-family: "Courier New"; @@ -272,16 +269,15 @@ class ActionHistory(QtWidgets.QPushButton): widget.addItem(item) # Show history - width = 40 + (largest_label_num_chars * 7) # padding + icon + text - entry_height = 21 - height = entry_height * len(self._history) - dialog = QtWidgets.QDialog(parent=self) dialog.setWindowTitle("Action History") - dialog.setWindowFlags(QtCore.Qt.FramelessWindowHint | - QtCore.Qt.Popup) - dialog.setSizePolicy(QtWidgets.QSizePolicy.Ignored, - QtWidgets.QSizePolicy.Ignored) + dialog.setWindowFlags( + QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup + ) + dialog.setSizePolicy( + QtWidgets.QSizePolicy.Ignored, + QtWidgets.QSizePolicy.Ignored + ) layout = QtWidgets.QVBoxLayout(dialog) layout.setContentsMargins(0, 0, 0, 0) @@ -294,10 +290,18 @@ class ActionHistory(QtWidgets.QPushButton): widget.clicked.connect(on_clicked) - dialog.setGeometry(point.x() - width, - point.y() - height, - width, - height) + # padding + icon + text + width = 40 + (largest_label_num_chars * 7) + entry_height = 21 + height = entry_height * len(self._history) + + point = QtGui.QCursor().pos() + dialog.setGeometry( + point.x() - width, + point.y() - height, + width, + height + ) dialog.exec_() self.widget_popup = widget @@ -306,11 +310,8 @@ class ActionHistory(QtWidgets.QPushButton): key = (action, copy.deepcopy(session)) # Remove entry if already exists - try: - index = self._history.index(key) - self._history.pop(index) - except ValueError: - pass + if key in self._history: + self._history.remove(key) self._history.append(key) @@ -319,7 +320,7 @@ class ActionHistory(QtWidgets.QPushButton): self._history = self._history[-self.max_history:] def clear_history(self): - self._history[:] = [] + self._history.clear() class SlidePageWidget(QtWidgets.QStackedWidget): From 92122bbe03bdffe43a712691bc9f0df8e254b2aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 22:23:14 +0200 Subject: [PATCH 353/947] moved icon caching to lib --- pype/tools/launcher/lib.py | 38 +++++++++++++++++++++++++++++++++++ pype/tools/launcher/models.py | 31 ++-------------------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index 8cd117074c..033ac33d66 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -14,7 +14,14 @@ provides a bridge between the file-based project inventory and configuration. """ +import os +from Qt import QtGui from avalon import io, lib, pipeline +from avalon.vendor import qtawesome +from pype.api import resources + +ICON_CACHE = {} +NOT_FOUND = type("NotFound", (object, ), {}) def list_project_tasks(): @@ -65,3 +72,34 @@ def get_application_actions(project): apps.append(action) return apps + + +def get_action_icon(self, action, skip_default=False): + icon_name = action.icon + if not icon_name: + return None + + global ICON_CACHE + + icon = ICON_CACHE.get(icon_name) + if icon is NOT_FOUND: + return None + elif icon: + return icon + + icon_path = resources.get_resource(icon_name) + if os.path.exists(icon_path): + icon = QtGui.QIcon(icon_path) + ICON_CACHE[icon_name] = icon + return icon + + try: + icon_color = getattr(action, "color", None) or "white" + icon = qtawesome.icon( + "fa.{}".format(icon_name), color=icon_color + ) + + except Exception: + print("Can't load icon \"{}\"".format(icon_name)) + + return icon diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index 7ad161236f..ce6e0c722e 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -7,7 +7,6 @@ from . import lib from Qt import QtCore, QtGui from avalon.vendor import qtawesome from avalon import io, style, api -from pype.api import resources log = logging.getLogger(__name__) @@ -112,8 +111,6 @@ class ActionModel(QtGui.QStandardItemModel): def __init__(self, parent=None): super(ActionModel, self).__init__(parent=parent) - self._icon_cache = {} - self._group_icon_cache = {} self._session = {} self._groups = {} self.default_icon = qtawesome.icon("fa.cube", color="white") @@ -139,33 +136,9 @@ class ActionModel(QtGui.QStandardItemModel): self._registered_actions = actions def get_icon(self, action, skip_default=False): - icon_name = action.icon - if not icon_name: - if skip_default: - return None + icon = lib.get_action_icon(action) + if not icon and not skip_default: return self.default_icon - - icon = self._icon_cache.get(icon_name) - if icon: - return icon - - icon = self.default_icon - icon_path = resources.get_resource(icon_name) - if os.path.exists(icon_path): - icon = QtGui.QIcon(icon_path) - self._icon_cache[icon_name] = icon - return icon - - try: - icon_color = getattr(action, "color", None) or "white" - icon = qtawesome.icon( - "fa.{}".format(icon_name), color=icon_color - ) - - except Exception: - print("Can't load icon \"{}\"".format(icon_name)) - - self._icon_cache[icon_name] = self.default_icon return icon def refresh(self): From b76d832d952b886bb7b1195580bd6d78f29e6cb0 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 Aug 2020 22:35:48 +0200 Subject: [PATCH 354/947] update families --- .../standalonepublisher/publish/collect_context.py | 4 ++-- .../publish/collect_matching_asset.py | 2 +- .../publish/collect_psd_instances.py | 10 +++++----- .../publish/extract_bg_for_compositing.py | 2 +- .../publish/extract_bg_main_groups.py | 2 +- .../publish/extract_images_from_psd.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 0e2d7fe2a6..a5479fdf13 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -48,7 +48,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): self.log.debug(f"_ in_data: {pformat(in_data)}") # exception for editorial - if in_data["family"] in ["editorial", "psd_batch"]: + if in_data["family"] in ["editorial", "background_batch"]: in_data_list = self.multiple_instances(context, in_data) else: in_data_list = [in_data] @@ -159,6 +159,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): if "psd" in component["name"]: instance.data["source"] = component["files"] - self.log.debug("Adding image:psd_batch family") + self.log.debug("Adding image:background_batch family") instance.data["representations"].append(component) diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index ef05131417..e8b1343c0e 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -14,7 +14,7 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): label = "Collect Matching Asset to Instance" order = pyblish.api.CollectorOrder - 0.05 hosts = ["standalonepublisher"] - family = ["psd_batch"] + family = ["background_batch"] def process(self, instance): source_file = os.path.basename(instance.data["source"]).lower() diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index d70c3a638b..9c8e2eae83 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -11,17 +11,17 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): label = "Collect Psd Instances" order = pyblish.api.CollectorOrder + 0.492 hosts = ["standalonepublisher"] - families = ["psd_batch"] + families = ["background_batch"] # presets subsets = { - "imageForLayout": { + "backgroundLayout": { "task": "background", - "family": "imageForLayout" + "family": "backgroundLayout" }, - "imageForComp": { + "backgroundComp": { "task": "background", - "family": "imageForComp" + "family": "backgroundComp" }, "workfileBackground": { "task": "background", diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index 7a1b86ef50..d4d625097a 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -9,7 +9,7 @@ PSDImage = None class ExtractBGForComp(pype.api.Extractor): label = "Extract Background for Compositing" - families = ["imageForComp"] + families = ["backgroundComp"] hosts = ["standalonepublisher"] new_instance_family = "background" diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py index 2dc89785da..e00f5858ed 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -11,7 +11,7 @@ PSDImage = None class ExtractBGMainGroups(pype.api.Extractor): label = "Extract Background Main Groups" order = pyblish.api.ExtractorOrder + 0.02 - families = ["imageForLayout"] + families = ["backgroundLayout"] hosts = ["standalonepublisher"] new_instance_family = "background" diff --git a/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py index 2e7c9acb15..5a2109478c 100644 --- a/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py +++ b/pype/plugins/standalonepublisher/publish/extract_images_from_psd.py @@ -14,7 +14,7 @@ class ExtractImagesFromPSD(pype.api.Extractor): active = False label = "Extract Images from PSD" order = pyblish.api.ExtractorOrder + 0.02 - families = ["imageForLayout"] + families = ["backgroundLayout"] hosts = ["standalonepublisher"] new_instance_family = "image" From 2ddd49ed4c97da1658e75a87557f8ee369f87870 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 Aug 2020 23:04:19 +0200 Subject: [PATCH 355/947] limit resources to explicit families --- .../global/publish/collect_resources_path.py | 34 +++++++++++++++++++ .../publish/collect_matching_asset.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_resources_path.py b/pype/plugins/global/publish/collect_resources_path.py index 734d1f84e4..04a33cd5be 100644 --- a/pype/plugins/global/publish/collect_resources_path.py +++ b/pype/plugins/global/publish/collect_resources_path.py @@ -20,6 +20,40 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): label = "Collect Resources Path" order = pyblish.api.CollectorOrder + 0.495 + families = ["workfile", + "pointcache", + "camera", + "animation", + "model", + "mayaAscii", + "setdress", + "layout", + "ass", + "vdbcache", + "scene", + "vrayproxy", + "render", + "prerender", + "imagesequence", + "rendersetup", + "rig", + "plate", + "look", + "lut", + "yetiRig", + "yeticache", + "nukenodes", + "gizmo", + "source", + "matchmove", + "image", + "source", + "assembly", + "fbx", + "textures", + "action", + "background" + ] def process(self, instance): anatomy = instance.context.data["anatomy"] diff --git a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py index e8b1343c0e..48065c4662 100644 --- a/pype/plugins/standalonepublisher/publish/collect_matching_asset.py +++ b/pype/plugins/standalonepublisher/publish/collect_matching_asset.py @@ -14,7 +14,7 @@ class CollectMatchingAssetToInstance(pyblish.api.InstancePlugin): label = "Collect Matching Asset to Instance" order = pyblish.api.CollectorOrder - 0.05 hosts = ["standalonepublisher"] - family = ["background_batch"] + families = ["background_batch"] def process(self, instance): source_file = os.path.basename(instance.data["source"]).lower() From 1fcb4e836f18f8e9ad6a0a5c160d812f0633c10a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 12 Aug 2020 23:10:40 +0200 Subject: [PATCH 356/947] small tweaks --- pype/tools/launcher/actions.py | 25 ++- pype/tools/launcher/app.py | 270 +++++++++++++----------------- pype/tools/launcher/flickcharm.py | 1 - pype/tools/launcher/lib.py | 2 +- 4 files changed, 137 insertions(+), 161 deletions(-) diff --git a/pype/tools/launcher/actions.py b/pype/tools/launcher/actions.py index 2a2e2ab0f0..44ba9a3a60 100644 --- a/pype/tools/launcher/actions.py +++ b/pype/tools/launcher/actions.py @@ -15,9 +15,13 @@ class ProjectManagerAction(api.Action): return "AVALON_PROJECT" in session def process(self, session, **kwargs): - return lib.launch(executable="python", - args=["-u", "-m", "avalon.tools.projectmanager", - session['AVALON_PROJECT']]) + return lib.launch( + executable="python", + args=[ + "-u", "-m", "avalon.tools.projectmanager", + session['AVALON_PROJECT'] + ] + ) class LoaderAction(api.Action): @@ -31,9 +35,12 @@ class LoaderAction(api.Action): return "AVALON_PROJECT" in session def process(self, session, **kwargs): - return lib.launch(executable="python", - args=["-u", "-m", "avalon.tools.cbloader", - session['AVALON_PROJECT']]) + return lib.launch( + executable="python", + args=[ + "-u", "-m", "avalon.tools.cbloader", session['AVALON_PROJECT'] + ] + ) class LoaderLibrary(api.Action): @@ -46,8 +53,10 @@ class LoaderLibrary(api.Action): return True def process(self, session, **kwargs): - return lib.launch(executable="python", - args=["-u", "-m", "avalon.tools.libraryloader"]) + return lib.launch( + executable="python", + args=["-u", "-m", "avalon.tools.libraryloader"] + ) def register_default_actions(): diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index abc350641b..78c5406fa8 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -110,55 +110,59 @@ class ProjectsPanel(QtWidgets.QWidget): def on_clicked(self, index): if index.isValid(): - project = index.data(QtCore.Qt.DisplayRole) - self.project_clicked.emit(project) + project_name = index.data(QtCore.Qt.DisplayRole) + self.project_clicked.emit(project_name) class AssetsPanel(QtWidgets.QWidget): """Assets page""" - back_clicked = QtCore.Signal() def __init__(self, parent=None): super(AssetsPanel, self).__init__(parent=parent) # project bar - project_bar = QtWidgets.QWidget() - layout = QtWidgets.QHBoxLayout(project_bar) + project_bar_widget = QtWidgets.QWidget() + + layout = QtWidgets.QHBoxLayout(project_bar_widget) layout.setSpacing(4) - icon = qtawesome.icon("fa.angle-left", color="white") - back = QtWidgets.QPushButton() - back.setIcon(icon) - back.setFixedWidth(23) - back.setFixedHeight(23) - projects = ProjectBar() - layout.addWidget(back) - layout.addWidget(projects) + btn_back_icon = qtawesome.icon("fa.angle-left", color="white") + btn_back = QtWidgets.QPushButton() + btn_back.setIcon(btn_back_icon) + btn_back.setFixedWidth(23) + btn_back.setFixedHeight(23) + + project_bar = ProjectBar() + + layout.addWidget(btn_back) + layout.addWidget(project_bar) # assets assets_proxy_widgets = QtWidgets.QWidget() assets_proxy_widgets.setContentsMargins(0, 0, 0, 0) assets_layout = QtWidgets.QVBoxLayout(assets_proxy_widgets) - assets_widgets = AssetWidget() + assets_widget = AssetWidget() # Make assets view flickable flick = FlickCharm(parent=self) - flick.activateOn(assets_widgets.view) - assets_widgets.view.setVerticalScrollMode( - assets_widgets.view.ScrollPerPixel + flick.activateOn(assets_widget.view) + assets_widget.view.setVerticalScrollMode( + assets_widget.view.ScrollPerPixel ) - assets_layout.addWidget(assets_widgets) + assets_layout.addWidget(assets_widget) # tasks - tasks_widgets = TasksWidget() + tasks_widget = TasksWidget() body = QtWidgets.QSplitter() body.setContentsMargins(0, 0, 0, 0) - body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) + body.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding + ) body.setOrientation(QtCore.Qt.Horizontal) body.addWidget(assets_proxy_widgets) - body.addWidget(tasks_widgets) + body.addWidget(tasks_widget) body.setStretchFactor(0, 100) body.setStretchFactor(1, 65) @@ -166,49 +170,38 @@ class AssetsPanel(QtWidgets.QWidget): layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) - layout.addWidget(project_bar) + layout.addWidget(project_bar_widget) layout.addWidget(body) - self.data = { - "model": { - "projects": projects, - "assets": assets_widgets, - "tasks": tasks_widgets - }, - } + self.project_bar = project_bar + self.assets_widget = assets_widget + self.tasks_widget = tasks_widget # signals - projects.project_changed.connect(self.on_project_changed) - assets_widgets.selection_changed.connect(self.asset_changed) - back.clicked.connect(self.back_clicked) + project_bar.project_changed.connect(self.on_project_changed) + assets_widget.selection_changed.connect(self.on_asset_changed) + btn_back.clicked.connect(self.back_clicked) # Force initial refresh for the assets since we might not be # trigging a Project switch if we click the project that was set # prior to launching the Launcher # todo: remove this behavior when AVALON_PROJECT is not required - assets_widgets.refresh() + assets_widget.refresh() def set_project(self, project): - - projects = self.data["model"]["projects"] - - before = projects.get_current_project() - projects.set_project(project) + before = self.project_bar.get_current_project() + self.project_bar.set_project(project) if project == before: # Force a refresh on the assets if the project hasn't changed - self.data["model"]["assets"].refresh() - - def asset_changed(self): - tools_lib.schedule(self.on_asset_changed, 0.05, - channel="assets") + self.assets_widget.refresh() def on_project_changed(self): - project_name = self.data["model"]["projects"].get_current_project() + project_name = self.project_bar.get_current_project() api.Session["AVALON_PROJECT"] = project_name - self.data["model"]["assets"].refresh() + self.assets_widget.refresh() # Force asset change callback to ensure tasks are correctly reset - self.asset_changed() + tools_lib.schedule(self.on_asset_changed, 0.05, channel="assets") def on_asset_changed(self): """Callback on asset selection changed @@ -219,21 +212,14 @@ class AssetsPanel(QtWidgets.QWidget): print("Asset changed..") - tasks = self.data["model"]["tasks"] - assets = self.data["model"]["assets"] - - asset = assets.get_active_asset_document() - if asset: - tasks.set_asset(asset["_id"]) + asset_doc = self.assets_widget.get_active_asset_document() + if asset_doc: + self.tasks_widget.set_asset(asset_doc["_id"]) else: - tasks.set_asset(None) + self.tasks_widget.set_asset(None) - def _get_current_session(self): - - tasks = self.data["model"]["tasks"] - assets = self.data["model"]["assets"] - - asset = assets.get_active_asset_document() + def get_current_session(self): + asset_doc = self.assets_widget.get_active_asset_document() session = copy.deepcopy(api.Session) # Clear some values that we are about to collect if available @@ -241,16 +227,11 @@ class AssetsPanel(QtWidgets.QWidget): session.pop("AVALON_ASSET", None) session.pop("AVALON_TASK", None) - if asset: - session["AVALON_ASSET"] = asset["name"] - - silo = asset.get("silo") - if silo: - session["AVALON_SILO"] = silo - - task = tasks.get_current_task() - if task: - session["AVALON_TASK"] = task + if asset_doc: + session["AVALON_ASSET"] = asset_doc["name"] + task_name = self.tasks_widget.get_current_task() + if task_name: + session["AVALON_TASK"] = task_name return session @@ -273,21 +254,24 @@ class Window(QtWidgets.QDialog): project_panel = ProjectsPanel() asset_panel = AssetsPanel() - pages = SlidePageWidget() - pages.addWidget(project_panel) - pages.addWidget(asset_panel) + page_slider = SlidePageWidget() + page_slider.addWidget(project_panel) + page_slider.addWidget(asset_panel) # actions - actions = ActionBar() + actions_bar = ActionBar() # statusbar statusbar = QtWidgets.QWidget() - message = QtWidgets.QLabel() - message.setFixedHeight(15) + layout = QtWidgets.QHBoxLayout(statusbar) + + message_label = QtWidgets.QLabel() + message_label.setFixedHeight(15) + action_history = ActionHistory() action_history.setStatusTip("Show Action History") - layout = QtWidgets.QHBoxLayout(statusbar) - layout.addWidget(message) + + layout.addWidget(message_label) layout.addWidget(action_history) # Vertically split Pages and Actions @@ -296,8 +280,8 @@ class Window(QtWidgets.QDialog): body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) body.setOrientation(QtCore.Qt.Vertical) - body.addWidget(pages) - body.addWidget(actions) + body.addWidget(page_slider) + body.addWidget(actions_bar) # Set useful default sizes and set stretch # for the pages so that is the only one that @@ -311,73 +295,71 @@ class Window(QtWidgets.QDialog): layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) + self.message_label = message_label + self.project_panel = project_panel + self.asset_panel = asset_panel + self.actions_bar = actions_bar + self.action_history = action_history + self.data = { - "label": { - "message": message, - }, "pages": { "project": project_panel, "asset": asset_panel }, "model": { - "actions": actions, + "actions": actions_bar, "action_history": action_history }, } - self.pages = pages + self.page_slider = page_slider self._page = 0 # signals - actions.action_clicked.connect(self.on_action_clicked) + actions_bar.action_clicked.connect(self.on_action_clicked) action_history.trigger_history.connect(self.on_history_action) project_panel.project_clicked.connect(self.on_project_clicked) asset_panel.back_clicked.connect(self.on_back_clicked) # Add some signals to propagate from the asset panel - for signal in [ - asset_panel.data["model"]["projects"].project_changed, - asset_panel.data["model"]["assets"].selection_changed, - asset_panel.data["model"]["tasks"].task_changed - ]: + for signal in ( + asset_panel.project_bar.project_changed, + asset_panel.assets_widget.selection_changed, + asset_panel.tasks_widget.task_changed + ): signal.connect(self.on_session_changed) # todo: Simplify this callback connection - asset_panel.data["model"]["projects"].project_changed.connect( + asset_panel.project_bar.project_changed.connect( self.on_project_changed ) self.resize(520, 740) def set_page(self, page): - - current = self.pages.currentIndex() + current = self.page_slider.currentIndex() if current == page and self._page == page: return direction = "right" if page > current else "left" self._page = page - self.pages.slide_view(page, direction=direction) + self.page_slider.slide_view(page, direction=direction) def refresh(self): - asset = self.data["pages"]["asset"] - asset.data["model"]["assets"].refresh() + self.asset_panel.assets_widget.refresh() self.refresh_actions() def echo(self, message): - widget = self.data["label"]["message"] - widget.setText(str(message)) - - QtCore.QTimer.singleShot(5000, lambda: widget.setText("")) - + self.message_label.setText(str(message)) + QtCore.QTimer.singleShot(5000, lambda: self.message_label.setText("")) print(message) def on_project_changed(self): - project_name = self.data["pages"]["asset"].data["model"]["projects"].get_current_project() + project_name = self.asset_panel.project_bar.get_current_project() io.Session["AVALON_PROJECT"] = project_name # Update the Action plug-ins available for the current project - self.data["model"]["actions"].model.discover() + self.actions_bar.model.discover() def on_session_changed(self): self.refresh_actions() @@ -385,26 +367,23 @@ class Window(QtWidgets.QDialog): def refresh_actions(self, delay=1): tools_lib.schedule(self.on_refresh_actions, delay) - def on_project_clicked(self, project): - io.Session["AVALON_PROJECT"] = project - asset_panel = self.data["pages"]["asset"] - asset_panel.data["model"]["projects"].refresh() # Refresh projects - asset_panel.set_project(project) + def on_project_clicked(self, project_name): + io.Session["AVALON_PROJECT"] = project_name + # Refresh projects + self.asset_panel.project_bar.refresh() + self.asset_panel.set_project(project_name) self.set_page(1) self.refresh_actions() def on_back_clicked(self): - self.set_page(0) - self.data["pages"]["project"].model.refresh() # Refresh projects + self.project_panel.model.refresh() # Refresh projects self.refresh_actions() def on_refresh_actions(self): session = self.get_current_session() - - actions = self.data["model"]["actions"] - actions.model.set_session(session) - actions.model.refresh() + self.actions_bar.model.set_session(session) + self.actions_bar.model.refresh() def on_action_clicked(self, action): self.echo("Running action: %s" % action.name) @@ -424,69 +403,58 @@ class Window(QtWidgets.QDialog): self.set_session(session) def get_current_session(self): - - index = self._page - if index == 1: + if self._page == 1: # Assets page - return self.data["pages"]["asset"]._get_current_session() + return self.asset_panel.get_current_session() - else: - session = copy.deepcopy(api.Session) + session = copy.deepcopy(api.Session) - # Remove some potential invalid session values - # that we know are not set when not browsing in - # a project. - session.pop("AVALON_PROJECT", None) - session.pop("AVALON_ASSET", None) - session.pop("AVALON_SILO", None) - session.pop("AVALON_TASK", None) + # Remove some potential invalid session values + # that we know are not set when not browsing in + # a project. + session.pop("AVALON_PROJECT", None) + session.pop("AVALON_ASSET", None) + session.pop("AVALON_SILO", None) + session.pop("AVALON_TASK", None) - return session + return session def run_action(self, action, session=None): - if session is None: session = self.get_current_session() # Add to history - history = self.data["model"]["action_history"] - history.add_action(action, session) + self.action_history.add_action(action, session) # Process the Action action().process(session) def set_session(self, session): - - panel = self.data["pages"]["asset"] - - project = session.get("AVALON_PROJECT") + project_name = session.get("AVALON_PROJECT") silo = session.get("AVALON_SILO") - asset = session.get("AVALON_ASSET") - task = session.get("AVALON_TASK") - - if project: + asset_name = session.get("AVALON_ASSET") + task_name = session.get("AVALON_TASK") + if project_name: # Force the "in project" view. self.pages.slide_view(1, direction="right") - - projects = panel.data["model"]["projects"] - index = projects.view.findText(project) + index = self.asset_panel.project_bar.view.findText(project_name) if index >= 0: - projects.view.setCurrentIndex(index) + self.asset_panel.project_bar.view.setCurrentIndex(index) if silo: - panel.data["model"]["assets"].set_silo(silo) + self.asset_panel.assets_widget.set_silo(silo) - if asset: - panel.data["model"]["assets"].select_assets([asset]) + if asset_name: + self.asset_panel.assets_widget.select_assets([asset_name]) - if task: - panel.on_asset_changed() # requires a forced refresh first - panel.data["model"]["tasks"].select_task(task) + if task_name: + # requires a forced refresh first + self.asset_panel.on_asset_changed() + self.asset_panel.assets_widget.select_task(task_name) class Application(QtWidgets.QApplication): - def __init__(self, *args): super(Application, self).__init__(*args) diff --git a/pype/tools/launcher/flickcharm.py b/pype/tools/launcher/flickcharm.py index b4dd69be6c..a5ea5a79d8 100644 --- a/pype/tools/launcher/flickcharm.py +++ b/pype/tools/launcher/flickcharm.py @@ -16,7 +16,6 @@ travelled only very slightly with the cursor. """ import copy -import sys from Qt import QtWidgets, QtCore, QtGui diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index 033ac33d66..e7933e9843 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -74,7 +74,7 @@ def get_application_actions(project): return apps -def get_action_icon(self, action, skip_default=False): +def get_action_icon(action): icon_name = action.icon if not icon_name: return None From 6b3233c2299e76e34c04656b50c64a5c2690c75b Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 12 Aug 2020 23:17:07 +0200 Subject: [PATCH 357/947] bump version --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index d70304e62c..0d4f03098b 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.11.4" +__version__ = "2.11.5" From 6e25e483be3840a451b243fce0e88260a44a6ccf Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 13 Aug 2020 09:44:43 +0100 Subject: [PATCH 358/947] Use plugin attributes for legacy. --- pype/plugins/maya/create/create_review.py | 2 -- pype/plugins/maya/publish/collect_review.py | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/plugins/maya/create/create_review.py b/pype/plugins/maya/create/create_review.py index f05271aeb2..3e513032e1 100644 --- a/pype/plugins/maya/create/create_review.py +++ b/pype/plugins/maya/create/create_review.py @@ -21,6 +21,4 @@ class CreateReview(avalon.maya.Creator): for key, value in animation_data.items(): data[key] = value - data["legacy"] = True - self.data = data diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 4d86c6031d..e2df54c10b 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -13,6 +13,7 @@ class CollectReview(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.3 label = 'Collect Review Data' families = ["review"] + legacy = True def process(self, instance): @@ -69,7 +70,7 @@ class CollectReview(pyblish.api.InstancePlugin): instance.data['remove'] = True i += 1 else: - if instance.data.get("legacy", True): + if self.legacy: instance.data['subset'] = task + 'Review' else: subset = "{}{}{}".format( From 0badfc0a5ace0cebc8252122f1e14b4a521a15c2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 11:22:07 +0200 Subject: [PATCH 359/947] adde unsaved changes dialog with 3 possible results --- pype/tools/config_setting/widgets/widgets.py | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pype/tools/config_setting/widgets/widgets.py b/pype/tools/config_setting/widgets/widgets.py index 34fdfde1a5..1d30a67e28 100644 --- a/pype/tools/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/widgets/widgets.py @@ -103,3 +103,40 @@ class ExpandingWidget(QtWidgets.QWidget): def resizeEvent(self, event): super(ExpandingWidget, self).resizeEvent(event) self.content_widget.updateGeometry() + + +class UnsavedChangesDialog(QtWidgets.QDialog): + message = "My message" + + def __init__(self, parent=None): + super().__init__(parent) + message_label = QtWidgets.QLabel(self.message) + + btns_widget = QtWidgets.QWidget(self) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + + btn_ok = QtWidgets.QPushButton("OK") + btn_ok.clicked.connect(self.on_ok_pressed) + btn_discard = QtWidgets.QPushButton("Discard changes") + btn_discard.clicked.connect(self.on_discard_pressed) + btn_cancel = QtWidgets.QPushButton("Cancel") + btn_cancel.clicked.connect(self.on_cancel_pressed) + + btns_layout.addWidget(btn_ok) + btns_layout.addWidget(btn_discard) + btns_layout.addWidget(btn_cancel) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(message_label) + layout.addWidget(btns_widget) + + self.state = None + + def on_cancel_pressed(self): + self.done(0) + + def on_ok_pressed(self): + self.done(1) + + def on_discard_pressed(self): + self.done(2) From 242c55716174fdc9482733098144aae14681db34 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 11:24:30 +0200 Subject: [PATCH 360/947] modified dialog --- pype/tools/config_setting/widgets/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/widgets/widgets.py b/pype/tools/config_setting/widgets/widgets.py index 1d30a67e28..3d5528e17a 100644 --- a/pype/tools/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/widgets/widgets.py @@ -106,7 +106,7 @@ class ExpandingWidget(QtWidgets.QWidget): class UnsavedChangesDialog(QtWidgets.QDialog): - message = "My message" + message = "You have unsaved changes. What do you want to do with them?" def __init__(self, parent=None): super().__init__(parent) @@ -115,9 +115,9 @@ class UnsavedChangesDialog(QtWidgets.QDialog): btns_widget = QtWidgets.QWidget(self) btns_layout = QtWidgets.QHBoxLayout(btns_widget) - btn_ok = QtWidgets.QPushButton("OK") + btn_ok = QtWidgets.QPushButton("Save") btn_ok.clicked.connect(self.on_ok_pressed) - btn_discard = QtWidgets.QPushButton("Discard changes") + btn_discard = QtWidgets.QPushButton("Discard") btn_discard.clicked.connect(self.on_discard_pressed) btn_cancel = QtWidgets.QPushButton("Cancel") btn_cancel.clicked.connect(self.on_cancel_pressed) From 1eb5b1ed2b3cb4aff3cf6d6eb89382fabbafa76e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 11:24:47 +0200 Subject: [PATCH 361/947] on project change is corrrect validation with dialog --- pype/tools/config_setting/widgets/base.py | 28 ++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 9413b07733..76fbad06c6 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -2,6 +2,7 @@ import os import json from Qt import QtWidgets, QtCore, QtGui from . import config +from .widgets import UnsavedChangesDialog from .lib import NOT_SET from avalon import io from queue import Queue @@ -217,16 +218,37 @@ class ProjectListWidget(QtWidgets.QWidget): if self.current_project == new_project_name: return + save_changes = False + change_project = False if self.validate_context_change(): + change_project = True + + else: + dialog = UnsavedChangesDialog(self) + result = dialog.exec_() + if result == 1: + save_changes = True + change_project = True + + elif result == 2: + change_project = True + + if save_changes: + self._parent._save() + + if change_project: self.select_project(new_project_name) self.current_project = new_project_name self.project_changed.emit() - return - - self.select_project(self.current_project) + else: + self.select_project(self.current_project) def validate_context_change(self): # TODO add check if project can be changed (is modified) + for item in self._parent.input_fields: + is_modified = item.child_modified + if is_modified: + return False return True def project_name(self): From cd06e2e95128ad9c1a105cc766384a105d341e12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 11:26:45 +0200 Subject: [PATCH 362/947] removed psd from images extension to not group them in standalone publisher --- .../standalonepublish/widgets/widget_drop_frame.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/modules/standalonepublish/widgets/widget_drop_frame.py index 785f6adce4..bbe8a0bff5 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_frame.py @@ -18,7 +18,7 @@ class DropDataFrame(QtWidgets.QFrame): ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", - ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras", + ".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras", ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", ".xpm", ".xwd" @@ -37,7 +37,6 @@ class DropDataFrame(QtWidgets.QFrame): "image_file": image_extensions, "video_file": video_extensions } - ffprobe_ignore_extensions = [".psd"] def __init__(self, parent): super().__init__() @@ -284,12 +283,7 @@ class DropDataFrame(QtWidgets.QFrame): if 'file_info' in data: file_info = data['file_info'] - if ( - ext not in self.ffprobe_ignore_extensions - and ( - ext in self.image_extensions or ext in self.video_extensions - ) - ): + if ext in self.image_extensions or ext in self.video_extensions: probe_data = self.load_data_with_probe(filepath) if 'fps' not in data: # default value From 43ad15ccb2e4220b07b076ca97bb9dc999957300 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 11:27:58 +0200 Subject: [PATCH 363/947] added layer index to filename in main groups exporter --- .../standalonepublisher/publish/extract_bg_main_groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py index e00f5858ed..58704c41d2 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -117,7 +117,7 @@ class ExtractBGMainGroups(pype.api.Extractor): ).format(layer.name)) continue - filename = "{}.png".format(layer_name) + filename = "{:0>2}_{}.png".format(layer_idx, layer_name) layer_data = { "index": layer_idx, "name": layer.name, From 2b07663fee86e91ebf6d21ad4635eea0077f04ec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 11:41:56 +0200 Subject: [PATCH 364/947] added "is_file" key to schemas --- .../config_gui_schema/applications_gui_schema.json | 1 + .../config_gui_schema/ftrack_projects_gui_schema.json | 2 ++ .../config_setting/config_gui_schema/plugins_gui_schema.json | 1 + .../config_setting/config_gui_schema/test_project_schema.json | 1 + .../config_setting/config_gui_schema/tools_gui_schema.json | 1 + 5 files changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json b/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json index 096964b5b8..5c20e630fa 100644 --- a/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json @@ -2,6 +2,7 @@ "key": "applications", "type": "dict-expanding", "label": "Applications", + "is_file": true, "children": [ { "type": "dict-form", diff --git a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json index 4a35fc9b61..ac696d18d0 100644 --- a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json @@ -8,6 +8,7 @@ "type": "dict-expanding", "label": "Status updates", "is_group": true, + "is_file": true, "children": [ { "key": "Ready", @@ -24,6 +25,7 @@ "type": "dict-expanding", "label": "Version status to Task status", "is_group": true, + "is_file": true, "children": [ { "key": "in progress", diff --git a/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json b/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json index 6c83fee172..1eaecbb8e5 100644 --- a/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json @@ -13,6 +13,7 @@ "type": "dict-expanding", "label": "Publish plugins", "is_group": true, + "is_file": true, "children": [ { "key": "ValidateModelName", diff --git a/pype/tools/config_setting/config_gui_schema/test_project_schema.json b/pype/tools/config_setting/config_gui_schema/test_project_schema.json index e789b422a3..43c9a647f4 100644 --- a/pype/tools/config_setting/config_gui_schema/test_project_schema.json +++ b/pype/tools/config_setting/config_gui_schema/test_project_schema.json @@ -3,6 +3,7 @@ "type": "dict-expanding", "label": "Test inputs", "is_group": true, + "is_file": true, "children": [ { "key": "boolean", diff --git a/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json b/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json index 2f46ef0ec5..3507d14a36 100644 --- a/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json @@ -2,6 +2,7 @@ "key": "tools", "type": "dict-expanding", "label": "Tools", + "is_file": true, "children": [ { "type": "dict-form", From 745e1cff968b70cf52dfd131c1def152b9136112 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 12:09:29 +0200 Subject: [PATCH 365/947] moved gui creation to reset method --- pype/tools/config_setting/widgets/base.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 76fbad06c6..f36e21483e 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -88,11 +88,6 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.content_layout = content_layout self.content_widget = content_widget - values = {"studio": config.studio_presets()} - schema = config.gui_schema("studio_gui_schema") - self.keys = schema.get("keys", []) - self.add_children_gui(schema, values) - footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) @@ -111,6 +106,20 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): save_btn.clicked.connect(self._save) + self.reset() + + def reset(self): + if self.content_layout.count() != 0: + for widget in self.input_fields: + self.content_layout.removeWidget(widget) + widget.deleteLater() + self.input_fields.clear() + + values = {"studio": config.studio_presets()} + schema = config.gui_schema("studio_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + def _save(self): all_values = {} for item in self.input_fields: @@ -354,6 +363,9 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.content_layout = content_layout self.content_widget = content_widget + self.reset() + + def reset(self): values = config.global_project_presets() schema = config.gui_schema("project_gui_schema") self.keys = schema.get("keys", []) From 20c6ef6a6c5648e70780d16345ad7abf276da18a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 12:37:47 +0200 Subject: [PATCH 366/947] separate gui schemas byt studio and project --- .../{ => projects_schema}/ftrack_projects_gui_schema.json | 0 .../{ => projects_schema}/plugins_gui_schema.json | 0 .../{ => projects_schema}/project_gui_schema.json | 0 .../{ => projects_schema}/test_project_schema.json | 0 .../{ => studio_schema}/applications_gui_schema.json | 0 .../config_gui_schema/{ => studio_schema}/studio_gui_schema.json | 0 .../config_gui_schema/{ => studio_schema}/tools_gui_schema.json | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename pype/tools/config_setting/config_gui_schema/{ => projects_schema}/ftrack_projects_gui_schema.json (100%) rename pype/tools/config_setting/config_gui_schema/{ => projects_schema}/plugins_gui_schema.json (100%) rename pype/tools/config_setting/config_gui_schema/{ => projects_schema}/project_gui_schema.json (100%) rename pype/tools/config_setting/config_gui_schema/{ => projects_schema}/test_project_schema.json (100%) rename pype/tools/config_setting/config_gui_schema/{ => studio_schema}/applications_gui_schema.json (100%) rename pype/tools/config_setting/config_gui_schema/{ => studio_schema}/studio_gui_schema.json (100%) rename pype/tools/config_setting/config_gui_schema/{ => studio_schema}/tools_gui_schema.json (100%) diff --git a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json rename to pype/tools/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json b/pype/tools/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/plugins_gui_schema.json rename to pype/tools/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json b/pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/project_gui_schema.json rename to pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/test_project_schema.json b/pype/tools/config_setting/config_gui_schema/projects_schema/test_project_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/test_project_schema.json rename to pype/tools/config_setting/config_gui_schema/projects_schema/test_project_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/applications_gui_schema.json rename to pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/studio_gui_schema.json rename to pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/tools_gui_schema.json rename to pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json From 17c1587631a9052c3a350b88b990d42a6e4312a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 12:38:25 +0200 Subject: [PATCH 367/947] gui_schema function also resolves inner schemas so inputs don't have to acrea about them --- pype/tools/config_setting/widgets/config.py | 56 +++++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 58b1e03a25..5aaa0e20ee 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -220,19 +220,53 @@ def project_presets(project_name=None, **kwargs): return apply_overrides(global_presets, project_overrides) -def gui_schema(schema_name): - filename = schema_name + ".json" - schema_folder = os.path.join( +def replace_inner_schemas(schema_data, schema_collection): + if schema_data["type"] == "schema": + raise ValueError("First item in schema data can't be schema.") + + children = schema_data.get("children") + if not children: + return schema_data + + new_children = [] + for child in children: + if child["type"] != "schema": + new_child = replace_inner_schemas(child, schema_collection) + new_children.append(new_child) + continue + + for schema_name in child["children"]: + new_child = replace_inner_schemas( + schema_collection[schema_name], + schema_collection + ) + new_children.append(new_child) + + schema_data["children"] = new_children + return schema_data + + +def gui_schema(subfolder, main_schema_name): + subfolder, main_schema_name + dirpath = os.path.join( os.path.dirname(os.path.dirname(__file__)), "config_gui_schema", - filename + subfolder ) - with open(schema_folder, "r") as json_stream: - schema = json.load(json_stream) - return schema + loaded_schemas = {} + for filename in os.listdir(dirpath): + basename, ext = os.path.splitext(filename) + if ext != ".json": + continue -p1 = studio_presets(with_metadata=True) -p2 = studio_presets(with_metadata=False) -print(json.dumps(p1, indent=4)) -print(json.dumps(p2, indent=4)) + filepath = os.path.join(dirpath, filename) + with open(filepath, "r") as json_stream: + schema_data = json.load(json_stream) + loaded_schemas[basename] = schema_data + + main_schema = replace_inner_schemas( + loaded_schemas[main_schema_name], + loaded_schemas + ) + return main_schema From c5730f915aa014f826e159fed9cb80dd8d8ecc32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 12:38:42 +0200 Subject: [PATCH 368/947] project and studio widgets use new gui_schema function --- pype/tools/config_setting/widgets/base.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index f36e21483e..134735974c 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -56,10 +56,6 @@ class PypeConfigurationWidget: class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): - config_dir = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - "config_gui_schema" - ) is_overidable = False is_overriden = False is_group = False @@ -116,7 +112,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.input_fields.clear() values = {"studio": config.studio_presets()} - schema = config.gui_schema("studio_gui_schema") + schema = config.gui_schema("studio_schema", "studio_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) @@ -301,10 +297,6 @@ class ProjectListWidget(QtWidgets.QWidget): class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): - config_dir = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - "config_gui_schema" - ) is_overriden = False is_group = False any_parent_is_group = False @@ -367,7 +359,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset(self): values = config.global_project_presets() - schema = config.gui_schema("project_gui_schema") + schema = config.gui_schema("projects_schema", "project_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) From ea433367ce44f9fdf4f03245ca1347aa37852482 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 12:42:59 +0200 Subject: [PATCH 369/947] minor changes --- .../config_gui_schema/studio_schema/applications_gui_schema.json | 1 + .../config_gui_schema/studio_schema/studio_gui_schema.json | 1 + .../config_gui_schema/studio_schema/tools_gui_schema.json | 1 + 3 files changed, 3 insertions(+) diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json index 5c20e630fa..12fbb3cc26 100644 --- a/pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json @@ -3,6 +3,7 @@ "type": "dict-expanding", "label": "Applications", "is_file": true, + "is_group": true, "children": [ { "type": "dict-form", diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json index 1a49735b8a..ba017760f3 100644 --- a/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json @@ -17,6 +17,7 @@ }, { "key": "muster", "type": "dict-invisible", + "is_group": true, "children": [{ "key": "templates_mapping", "label": "Muster", diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json index 3507d14a36..4c905a3826 100644 --- a/pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json @@ -2,6 +2,7 @@ "key": "tools", "type": "dict-expanding", "label": "Tools", + "is_group": true, "is_file": true, "children": [ { From 62a5e8ecdd771bf06ca46a2680da227154d3b4a6 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 13 Aug 2020 12:47:16 +0200 Subject: [PATCH 370/947] tweak default layer collection list --- .../standalonepublisher/publish/extract_bg_for_compositing.py | 2 +- .../standalonepublisher/publish/extract_bg_main_groups.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py index d4d625097a..064c226ff7 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_for_compositing.py @@ -16,7 +16,7 @@ class ExtractBGForComp(pype.api.Extractor): # Presetable allowed_group_names = [ - "OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide", "Field_Guide", + "OL", "BG", "MG", "FG", "SB", "UL", "SKY", "Field Guide", "Field_Guide", "ANIM" ] diff --git a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py index 58704c41d2..42530aeb14 100644 --- a/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py +++ b/pype/plugins/standalonepublisher/publish/extract_bg_main_groups.py @@ -9,7 +9,7 @@ PSDImage = None class ExtractBGMainGroups(pype.api.Extractor): - label = "Extract Background Main Groups" + label = "Extract Background Layout" order = pyblish.api.ExtractorOrder + 0.02 families = ["backgroundLayout"] hosts = ["standalonepublisher"] @@ -18,7 +18,7 @@ class ExtractBGMainGroups(pype.api.Extractor): # Presetable allowed_group_names = [ - "OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide", "Field_Guide", + "OL", "BG", "MG", "FG", "UL", "SB", "SKY", "Field Guide", "Field_Guide", "ANIM" ] From df491a9145b3262c748a03a796262ca5cb5ad375 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 12:52:48 +0200 Subject: [PATCH 371/947] skip collection if not ext in image extenstion --- .../widgets/widget_drop_frame.py | 114 ++++++++++-------- 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/modules/standalonepublish/widgets/widget_drop_frame.py index bbe8a0bff5..37d22cf887 100644 --- a/pype/modules/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/modules/standalonepublish/widgets/widget_drop_frame.py @@ -173,10 +173,21 @@ class DropDataFrame(QtWidgets.QFrame): def _process_paths(self, in_paths): self.parent_widget.working_start() paths = self._get_all_paths(in_paths) - collections, remainders = clique.assemble(paths) + collectionable_paths = [] + non_collectionable_paths = [] + for path in in_paths: + ext = os.path.splitext(path)[1] + if ext in self.image_extensions: + collectionable_paths.append(path) + else: + non_collectionable_paths.append(path) + + collections, remainders = clique.assemble(collectionable_paths) + non_collectionable_paths.extend(remainders) for collection in collections: self._process_collection(collection) - for remainder in remainders: + + for remainder in non_collectionable_paths: self._process_remainder(remainder) self.parent_widget.working_stop() @@ -341,61 +352,62 @@ class DropDataFrame(QtWidgets.QFrame): actions = [] found = False - for item in self.components_list.widgets(): - if data['ext'] != item.in_data['ext']: - continue - if data['folder_path'] != item.in_data['folder_path']: - continue - - ex_is_seq = item.in_data['is_sequence'] - - # If both are single files - if not new_is_seq and not ex_is_seq: - if data['name'] == item.in_data['name']: - found = True - break - paths = list(data['files']) - paths.extend(item.in_data['files']) - c, r = clique.assemble(paths) - if len(c) == 0: + if data["ext"] in self.image_extensions: + for item in self.components_list.widgets(): + if data['ext'] != item.in_data['ext']: continue - a_name = 'merge' - item.add_action(a_name) - if a_name not in actions: - actions.append(a_name) - - # If new is sequence and ex is single file - elif new_is_seq and not ex_is_seq: - if data['name'] not in item.in_data['name']: + if data['folder_path'] != item.in_data['folder_path']: continue - ex_file = item.in_data['files'][0] - a_name = 'merge' - item.add_action(a_name) - if a_name not in actions: - actions.append(a_name) - continue + ex_is_seq = item.in_data['is_sequence'] - # If new is single file existing is sequence - elif not new_is_seq and ex_is_seq: - if item.in_data['name'] not in data['name']: + # If both are single files + if not new_is_seq and not ex_is_seq: + if data['name'] == item.in_data['name']: + found = True + break + paths = list(data['files']) + paths.extend(item.in_data['files']) + c, r = clique.assemble(paths) + if len(c) == 0: + continue + a_name = 'merge' + item.add_action(a_name) + if a_name not in actions: + actions.append(a_name) + + # If new is sequence and ex is single file + elif new_is_seq and not ex_is_seq: + if data['name'] not in item.in_data['name']: + continue + ex_file = item.in_data['files'][0] + + a_name = 'merge' + item.add_action(a_name) + if a_name not in actions: + actions.append(a_name) continue - a_name = 'merge' - item.add_action(a_name) - if a_name not in actions: - actions.append(a_name) - # If both are sequence - else: - if data['name'] != item.in_data['name']: - continue - if data['files'] == list(item.in_data['files']): - found = True - break - a_name = 'merge' - item.add_action(a_name) - if a_name not in actions: - actions.append(a_name) + # If new is single file existing is sequence + elif not new_is_seq and ex_is_seq: + if item.in_data['name'] not in data['name']: + continue + a_name = 'merge' + item.add_action(a_name) + if a_name not in actions: + actions.append(a_name) + + # If both are sequence + else: + if data['name'] != item.in_data['name']: + continue + if data['files'] == list(item.in_data['files']): + found = True + break + a_name = 'merge' + item.add_action(a_name) + if a_name not in actions: + actions.append(a_name) if new_is_seq: actions.append('split') From 7f39070df4f468ff6568d4b6f16aa86c807f1431 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 13:12:34 +0200 Subject: [PATCH 372/947] enhance expanding dict states --- pype/tools/config_setting/widgets/inputs.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 839fe1e8a2..2c231e087a 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1271,7 +1271,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.any_parent_is_group = any_parent_is_group - self.is_modified = False + self._is_modified = False self._is_overriden = False self.is_group = is_group @@ -1394,9 +1394,9 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): if self.is_group: if self.is_overidable: self._is_overriden = True + # TODO update items if item is not None: - is_overriden = self.is_overriden for _item in self.input_fields: if _item is not item: _item.update_style() @@ -1416,12 +1416,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.style().polish(self) self._child_state = child_state - is_overriden = self.is_overriden - if child_modified and not is_overriden: - state = self.default_state - else: - state = self.style_state(is_overriden, child_modified) - + state = self.style_state(self.is_overriden, self.is_modified) if self._state == state: return @@ -1430,6 +1425,12 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._state = state + @property + def is_modified(self): + if self.is_group: + return self.child_modified + return False + @property def child_modified(self): for input_field in self.input_fields: From de62bc42d2680fdc5151cc5857dc3214d9a612b3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 15:15:15 +0200 Subject: [PATCH 373/947] added schema validations --- .../studio_schema/studio_gui_schema.json | 1 + pype/tools/config_setting/widgets/config.py | 52 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json index ba017760f3..088c407804 100644 --- a/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json @@ -21,6 +21,7 @@ "children": [{ "key": "templates_mapping", "label": "Muster", + "is_file": true, "type": "dict-modifiable", "object_type": "int" }] diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 5aaa0e20ee..3604316131 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -132,8 +132,6 @@ def load_jsons_from_dir(path, *args, **kwargs): sub_keys.pop(0) base_len = len(path) + 1 - ext_len = len(".json") - for base, _directories, filenames in os.walk(path): for filename in filenames: basename, ext = os.path.splitext(filename) @@ -246,6 +244,55 @@ def replace_inner_schemas(schema_data, schema_collection): return schema_data +class ShemaMissingFileInfo(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Schema has missing definition of output file (\"is_file\" key)" + " for keys. [{}]" + ).format(", ".join(full_path_keys)) + super(ShemaMissingFileInfo, self).__init__(msg) + + +def validate_all_has_ending_file(schema_data, is_top=True): + if schema_data.get("is_file"): + return None + + children = schema_data.get("children") + if not children: + return [[schema_data["key"]]] + + invalid = [] + keyless = "key" not in schema_data + for child in children: + result = validate_all_has_ending_file(child, False) + if result is None: + continue + + if keyless: + invalid.extend(result) + else: + for item in result: + new_invalid = [schema_data["key"]] + new_invalid.extend(item) + invalid.append(new_invalid) + + if not invalid: + return None + + if not is_top: + return invalid + + raise ShemaMissingFileInfo(invalid) + + +def validate_schema(schema_data): + validate_all_has_ending_file(schema_data) + + def gui_schema(subfolder, main_schema_name): subfolder, main_schema_name dirpath = os.path.join( @@ -269,4 +316,5 @@ def gui_schema(subfolder, main_schema_name): loaded_schemas[main_schema_name], loaded_schemas ) + validate_schema(main_schema) return main_schema From e2fdb92a97296c10fb796113c6bcff4f7de93afa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 15:52:05 +0200 Subject: [PATCH 374/947] changed way of storing data --- pype/tools/config_setting/widgets/base.py | 59 ++++++++++++----------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 134735974c..830902f6bb 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -115,6 +115,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): schema = config.gui_schema("studio_schema", "studio_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) + self.schema = schema def _save(self): all_values = {} @@ -129,38 +130,38 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): all_values = all_values["studio"] # Load studio data with metadata - config_with_metadata = config.studio_presets_with_metadata() + current_presets = config.studio_presets() - print(json.dumps(config_with_metadata, indent=4)) + print(json.dumps(current_presets, indent=4)) print(json.dumps(all_values, indent=4)) - per_file_values = {} - process_queue = Queue() - for _key, _values in all_values.items(): - process_queue.put(( - config.studio_presets_path, _key, config_with_metadata, _values - )) - - while not process_queue.empty(): - path, key, metadata, values = process_queue.get() - new_path = os.path.join(path, key) - # TODO this should not be - if key in metadata: - key_metadata = metadata[key] - - if key_metadata["type"] == "file": - new_path += ".json" - per_file_values[new_path] = values - continue - - for new_key, new_values in values.items(): - process_queue.put( - (new_path, new_key, key_metadata["value"], new_values) - ) - - for file_path, file_values in per_file_values.items(): - with open(file_path, "w") as file_stream: - json.dump(file_values, file_stream, indent=4) + # per_file_values = {} + # process_queue = Queue() + # for _key, _values in all_values.items(): + # process_queue.put(( + # config.studio_presets_path, _key, config_with_metadata, _values + # )) + # + # while not process_queue.empty(): + # path, key, metadata, values = process_queue.get() + # new_path = os.path.join(path, key) + # # TODO this should not be + # if key in metadata: + # key_metadata = metadata[key] + # + # if key_metadata["type"] == "file": + # new_path += ".json" + # per_file_values[new_path] = values + # continue + # + # for new_key, new_values in values.items(): + # process_queue.put( + # (new_path, new_key, key_metadata["value"], new_values) + # ) + # + # for file_path, file_values in per_file_values.items(): + # with open(file_path, "w") as file_stream: + # json.dump(file_values, file_stream, indent=4) def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] From cf971151b2fc7270a2e851bddb2f01fdc900b82d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 18:00:51 +0200 Subject: [PATCH 375/947] studio can again safe changes --- pype/tools/config_setting/widgets/base.py | 55 ++++++++++----------- pype/tools/config_setting/widgets/config.py | 24 +++++++++ pype/tools/config_setting/widgets/inputs.py | 2 +- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 830902f6bb..3ca3b910b0 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -1,5 +1,6 @@ import os import json +import copy from Qt import QtWidgets, QtCore, QtGui from . import config from .widgets import UnsavedChangesDialog @@ -132,36 +133,32 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): # Load studio data with metadata current_presets = config.studio_presets() - print(json.dumps(current_presets, indent=4)) - print(json.dumps(all_values, indent=4)) + output = {} + keys_to_file = config.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + origin_values = current_presets + for key in key_sequence: + if key not in origin_values: + origin_values = {} + break + origin_values = origin_values[key] - # per_file_values = {} - # process_queue = Queue() - # for _key, _values in all_values.items(): - # process_queue.put(( - # config.studio_presets_path, _key, config_with_metadata, _values - # )) - # - # while not process_queue.empty(): - # path, key, metadata, values = process_queue.get() - # new_path = os.path.join(path, key) - # # TODO this should not be - # if key in metadata: - # key_metadata = metadata[key] - # - # if key_metadata["type"] == "file": - # new_path += ".json" - # per_file_values[new_path] = values - # continue - # - # for new_key, new_values in values.items(): - # process_queue.put( - # (new_path, new_key, key_metadata["value"], new_values) - # ) - # - # for file_path, file_values in per_file_values.items(): - # with open(file_path, "w") as file_stream: - # json.dump(file_values, file_stream, indent=4) + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + origin_values.update(new_values) + + output_path = os.path.join( + config.studio_presets_path, subpath + ) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(output_path, "w") as file_stream: + json.dump(origin_values, file_stream, indent=4) def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 3604316131..8319c3d51d 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -257,6 +257,27 @@ class ShemaMissingFileInfo(Exception): super(ShemaMissingFileInfo, self).__init__(msg) +def file_keys_from_schema(schema_data): + output = [] + keys = [] + key = schema_data.get("key") + if key: + keys.append(key) + + for child in schema_data["children"]: + if child.get("is_file"): + _keys = copy.deepcopy(keys) + _keys.append(child["key"]) + output.append(_keys) + continue + + for result in file_keys_from_schema(child): + _keys = copy.deepcopy(keys) + _keys.extend(result) + output.append(_keys) + return output + + def validate_all_has_ending_file(schema_data, is_top=True): if schema_data.get("is_file"): return None @@ -290,6 +311,9 @@ def validate_all_has_ending_file(schema_data, is_top=True): def validate_schema(schema_data): + # TODO validator for key uniquenes + # TODO validator that is_group key is not before is_file child + # TODO validator that is_group or is_file is not on child without key validate_all_has_ending_file(schema_data) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 2c231e087a..c4ec7a4347 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -16,7 +16,7 @@ class SchemeGroupHierarchyBug(Exception): if not msg: # TODO better message msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" - super(SchemeGroupHierarchyBug, self).__init(msg) + super(SchemeGroupHierarchyBug, self).__init__(msg) class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): From e9f4d1989019fb3f3e159eae734c21627982722f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 13 Aug 2020 17:08:46 +0100 Subject: [PATCH 376/947] Explicit optional isolate attribute. --- pype/plugins/maya/create/create_review.py | 2 ++ pype/plugins/maya/publish/extract_playblast.py | 2 +- pype/plugins/maya/publish/extract_thumbnail.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/create/create_review.py b/pype/plugins/maya/create/create_review.py index 3e513032e1..6b153396d6 100644 --- a/pype/plugins/maya/create/create_review.py +++ b/pype/plugins/maya/create/create_review.py @@ -21,4 +21,6 @@ class CreateReview(avalon.maya.Creator): for key, value in animation_data.items(): data[key] = value + data["isolate"] = False + self.data = data diff --git a/pype/plugins/maya/publish/extract_playblast.py b/pype/plugins/maya/publish/extract_playblast.py index 3c9811d4c4..91849567e3 100644 --- a/pype/plugins/maya/publish/extract_playblast.py +++ b/pype/plugins/maya/publish/extract_playblast.py @@ -79,7 +79,7 @@ class ExtractPlayblast(pype.api.Extractor): # Isolate view is requested by having objects in the set besides a # camera. - if len(instance.data["setMembers"]) > 1: + if instance.data.get("isolate"): preset["isolate"] = instance.data["setMembers"] with maintained_time(): diff --git a/pype/plugins/maya/publish/extract_thumbnail.py b/pype/plugins/maya/publish/extract_thumbnail.py index 2edd19a559..524fc1e17c 100644 --- a/pype/plugins/maya/publish/extract_thumbnail.py +++ b/pype/plugins/maya/publish/extract_thumbnail.py @@ -79,7 +79,7 @@ class ExtractThumbnail(pype.api.Extractor): # Isolate view is requested by having objects in the set besides a # camera. - if len(instance.data["setMembers"]) > 1: + if instance.data.get("isolate"): preset["isolate"] = instance.data["setMembers"] with maintained_time(): From 8b7d565208bf5ea76db9330c7a43b6e6f5669930 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 13 Aug 2020 18:13:10 +0200 Subject: [PATCH 377/947] smaller changes --- .../config/studio_presets/global/applications.json | 8 ++++++-- .../config/studio_presets/muster/templates_mapping.json | 2 +- pype/tools/config_setting/widgets/base.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config/studio_presets/global/applications.json b/pype/tools/config_setting/config/studio_presets/global/applications.json index 35e399444c..21693e5fee 100644 --- a/pype/tools/config_setting/config/studio_presets/global/applications.json +++ b/pype/tools/config_setting/config/studio_presets/global/applications.json @@ -35,5 +35,9 @@ "resolve_16": true, "shell": true, "storyboardpro_7": true, - "unreal_4.21": true -} + "unreal_4.21": true, + "celaction_local": true, + "celaction_remote": true, + "houdini_16.5": false, + "unreal_4.24": true +} \ No newline at end of file diff --git a/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json b/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json index 4edab9077d..0c09113515 100644 --- a/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json +++ b/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json @@ -16,4 +16,4 @@ "vector": 4, "vray": 37, "ffmpeg": 48 -} +} \ No newline at end of file diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 3ca3b910b0..14ff56aa2a 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -133,9 +133,9 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): # Load studio data with metadata current_presets = config.studio_presets() - output = {} keys_to_file = config.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: + # Skip first key key_sequence = key_sequence[1:] subpath = "/".join(key_sequence) + ".json" origin_values = current_presets From 7924d49afdf1006f6eb578a0a75855f90be251a5 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 13 Aug 2020 22:20:30 +0200 Subject: [PATCH 378/947] add ability to skip integration of rendered frames --- pype/plugins/global/publish/integrate_new.py | 3 +++ pype/plugins/global/publish/submit_publish_job.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 1c1be09a0a..4510fc2d04 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -276,6 +276,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): published_representations = {} for idx, repre in enumerate(instance.data["representations"]): + if "delete" in repre.get("tags", []): + continue + published_files = [] # create template data for Anatomy diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 1ea31d4a0b..a97abaf433 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -203,6 +203,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # script path for publish_filesequence.py publishing_script = None + # poor man exclusion + skip_integration_repre_list = [] + def _create_metadata_path(self, instance): ins_data = instance.data # Ensure output dir exists @@ -466,6 +469,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review"] if preview else [] } + # poor man exclusion + if ext in self.skip_integration_repre_list: + rep["tags"].append("delete") + self._solve_families(new_instance, preview) new_instance["representations"] = [rep] @@ -545,8 +552,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review", "preview"] if preview else [], } + # poor man exclusion + if ext in self.skip_integration_repre_list: + rep["tags"].append("delete") + if instance.get("multipartExr", False): - rep["tags"].append["multipartExr"] + rep["tags"].append("multipartExr") representations.append(rep) From 8d5c2750c0c91f696717a5b5e05e085feefb4ef6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 13:45:33 +0200 Subject: [PATCH 379/947] implemented task to version status event handler --- .../events/event_task_to_version_status.py | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 pype/modules/ftrack/events/event_task_to_version_status.py diff --git a/pype/modules/ftrack/events/event_task_to_version_status.py b/pype/modules/ftrack/events/event_task_to_version_status.py new file mode 100644 index 0000000000..e07be67b18 --- /dev/null +++ b/pype/modules/ftrack/events/event_task_to_version_status.py @@ -0,0 +1,222 @@ +import collections +from pype.modules.ftrack import BaseEvent + + +class TaskToVersionStatus(BaseEvent): + """Changes status of task's latest AssetVersions on its status change.""" + + # Attribute for caching session user id + _cached_user_id = None + + # Presets usage + asset_types_of_focus = [] + + def register(self, *args, **kwargs): + # Skip registration if attribute `asset_types_of_focus` is not set + modified_asset_types_of_focus = list() + if self.asset_types_of_focus: + if isinstance(self.asset_types_of_focus, str): + self.asset_types_of_focus = [self.asset_types_of_focus] + + for asset_type_name in self.asset_types_of_focus: + modified_asset_types_of_focus.append( + asset_type_name.lower() + ) + + if not modified_asset_types_of_focus: + raise Exception(( + "Event handler \"{}\" does not" + " have set presets for attribute \"{}\"" + ).format(self.__class__.__name__, "asset_types_of_focus")) + + self.asset_types_of_focus = modified_asset_types_of_focus + return super(TaskToVersionStatus, self).register(*args, **kwargs) + + def is_event_invalid(self, session, event): + # Cache user id of currently running session + if self._cached_user_id is None: + session_user_entity = session.query( + "User where username is \"{}\"".format(session.api_user) + ).first() + if not session_user_entity: + self.log.warning( + "Couldn't query Ftrack user with username \"{}\"".format( + session.api_user + ) + ) + return False + self._cached_user_id = session_user_entity["id"] + + # Skip processing if current session user was the user who created + # the event + user_info = event["source"].get("user") or {} + user_id = user_info.get("id") + + # Mark as invalid if user is unknown + if user_id is None: + return True + return user_id == self._cached_user_id + + def filter_event_entities(self, event): + # Filter if event contain relevant data + entities_info = event["data"].get("entities") + if not entities_info: + return + + filtered_entities = [] + for entity_info in entities_info: + # Care only about tasks + if entity_info.get("entityType") != "task": + continue + + # Care only about changes of status + changes = entity_info.get("changes") or {} + statusid_changes = changes.get("statusid") or {} + if ( + statusid_changes.get("new") is None + or statusid_changes.get("old") is None + ): + continue + + filtered_entities.append(entity_info) + + return filtered_entities + + def _get_ent_path(self, entity): + return "/".join( + [ent["name"] for ent in entity["link"]] + ) + + def launch(self, session, event): + '''Propagates status from version to task when changed''' + if self.is_event_invalid(session, event): + return + + filtered_entity_infos = self.filter_event_entities(event) + if not filtered_entity_infos: + return + + task_ids = [ + entity_info["entityId"] + for entity_info in filtered_entity_infos + ] + joined_ids = ",".join( + ["\"{}\"".format(entity_id) for entity_id in task_ids] + ) + + # Query tasks' AssetVersions + asset_versions = session.query(( + "AssetVersion where task_id in ({}) order by version descending" + ).format(joined_ids)).all() + + last_asset_version_by_task_id = ( + self.last_asset_version_by_task_id(asset_versions, task_ids) + ) + if not last_asset_version_by_task_id: + return + + # Query Task entities for last asset versions + joined_filtered_ids = ",".join([ + "\"{}\"".format(entity_id) + for entity_id in last_asset_version_by_task_id.keys() + ]) + task_entities = session.query( + "Task where id in ({})".format(joined_filtered_ids) + ).all() + if not task_entities: + return + + # Final process of changing statuses + av_statuses_by_low_name = self.asset_version_statuses(task_entities[0]) + for task_entity in task_entities: + task_id = task_entity["id"] + task_path = self._get_ent_path(task_entity) + task_status_name = task_entity["status"]["name"] + task_status_name_low = task_status_name.lower() + + last_asset_versions = last_asset_version_by_task_id[task_id] + for last_asset_version in last_asset_versions: + self.log.debug(( + "Trying to change status of last AssetVersion {}" + " for task \"{}\"" + ).format(last_asset_version["version"], task_path)) + + new_asset_version_status = av_statuses_by_low_name.get( + task_status_name_low + ) + # Skip if tasks status is not available to AssetVersion + if not new_asset_version_status: + self.log.debug(( + "AssetVersion does not have matching status to \"{}\"" + ).format(task_status_name)) + continue + + av_ent_path = task_path + " Asset {} AssetVersion {}".format( + last_asset_version["asset"]["name"], + last_asset_version["version"] + ) + + # Skip if current AssetVersion's status is same + current_status_name = last_asset_version["status"]["name"] + if current_status_name.lower() == task_status_name_low: + self.log.debug(( + "AssetVersion already has set status \"{}\". \"{}\"" + ).format(current_status_name, av_ent_path)) + continue + + # Change the status + try: + last_asset_version["status"] = new_asset_version_status + session.commit() + self.log.info("[ {} ] Status updated to [ {} ]".format( + av_ent_path, new_asset_version_status["name"] + )) + except Exception: + session.rollback() + self.log.warning( + "[ {} ]Status couldn't be set to \"{}\"".format( + av_ent_path, new_asset_version_status["name"] + ), + exc_info=True + ) + + def asset_version_statuses(self, entity): + project_entity = self.get_project_from_entity(entity) + project_schema = project_entity["project_schema"] + # Get all available statuses for Task + statuses = project_schema.get_statuses("AssetVersion") + # map lowered status name with it's object + av_statuses_by_low_name = { + status["name"].lower(): status for status in statuses + } + return av_statuses_by_low_name + + def last_asset_version_by_task_id(self, asset_versions, task_ids): + last_asset_version_by_task_id = collections.defaultdict(list) + last_version_by_task_id = {} + poping_entity_ids = set(task_ids) + for asset_version in asset_versions: + asset_type_name_low = ( + asset_version["asset"]["type"]["name"].lower() + ) + if asset_type_name_low not in self.asset_types_of_focus: + continue + + task_id = asset_version["task_id"] + last_version = last_version_by_task_id.get(task_id) + if last_version is None: + last_version_by_task_id[task_id] = asset_version["version"] + + elif last_version != asset_version["version"]: + poping_entity_ids.remove(task_id) + + if not poping_entity_ids: + break + + if task_id in poping_entity_ids: + last_asset_version_by_task_id[task_id].append(asset_version) + return last_asset_version_by_task_id + + +def register(session, plugins_presets): + TaskToVersionStatus(session, plugins_presets).register() From 2a0d0717ca8c9a0c326e9741c017ce872fbe0f36 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 14 Aug 2020 15:35:21 +0100 Subject: [PATCH 380/947] Optional skip review on renders. --- pype/plugins/global/publish/extract_jpeg.py | 4 ++++ pype/plugins/global/publish/extract_review.py | 4 ++++ pype/plugins/maya/create/create_render.py | 1 + pype/plugins/maya/publish/collect_render.py | 1 + 4 files changed, 10 insertions(+) diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index 9b775f8b6f..ae74370b06 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -26,6 +26,10 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): if instance.data.get("multipartExr") is True: return + # Skip review when requested. + if not instance.data.get("review"): + return + # get representation and loop them representations = instance.data["representations"] diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index e1a0d7043a..1b003064a1 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -50,6 +50,10 @@ class ExtractReview(pyblish.api.InstancePlugin): to_height = 1080 def process(self, instance): + # Skip review when requested. + if not instance.data.get("review"): + return + # ffmpeg doesn't support multipart exrs if instance.data.get("multipartExr") is True: instance_label = ( diff --git a/pype/plugins/maya/create/create_render.py b/pype/plugins/maya/create/create_render.py index 9e5f9310ae..9f05226f69 100644 --- a/pype/plugins/maya/create/create_render.py +++ b/pype/plugins/maya/create/create_render.py @@ -175,6 +175,7 @@ class CreateRender(avalon.maya.Creator): self.data["primaryPool"] = pool_names self.data["suspendPublishJob"] = False + self.data["review"] = True self.data["extendFrames"] = False self.data["overrideExistingFrame"] = True # self.data["useLegacyRenderLayers"] = True diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 5ca9392080..dfae6ed0af 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -216,6 +216,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "attachTo": attach_to, "setMembers": layer_name, "multipartExr": ef.multipart, + "review": render_instance.data.get("review") or False, "publish": True, "handleStart": handle_start, From e00091515c3010d2d9e6531ba4e8762011525cfb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 17:58:42 +0200 Subject: [PATCH 381/947] standalone publer moved to tools --- pype/modules/standalonepublish/__init__.py | 11 +- .../standalonepublish_module.py | 20 +- pype/tools/config_setting/widgets/base0.py | 404 ++++ pype/tools/config_setting/widgets/inputs0.py | 2145 +++++++++++++++++ pype/tools/config_setting/widgets/inputs1.py | 2131 ++++++++++++++++ pype/tools/standalonepublish/__init__.py | 8 + .../standalonepublish/__main__.py | 0 .../standalonepublish/app.py | 0 .../standalonepublish/publish.py | 0 .../standalonepublish/resources/__init__.py | 0 .../standalonepublish/resources/edit.svg | 0 .../standalonepublish/resources/file.png | Bin .../standalonepublish/resources/files.png | Bin .../standalonepublish/resources/houdini.png | Bin .../resources/image_file.png | Bin .../resources/image_files.png | Bin .../resources/information.svg | 0 .../standalonepublish/resources/maya.png | Bin .../standalonepublish/resources/menu.png | Bin .../resources/menu_disabled.png | Bin .../resources/menu_hover.png | Bin .../resources/menu_pressed.png | Bin .../resources/menu_pressed_hover.png | Bin .../standalonepublish/resources/nuke.png | Bin .../standalonepublish/resources/premiere.png | Bin .../standalonepublish/resources/trash.png | Bin .../resources/trash_disabled.png | Bin .../resources/trash_hover.png | Bin .../resources/trash_pressed.png | Bin .../resources/trash_pressed_hover.png | Bin .../resources/video_file.png | Bin .../standalonepublish/widgets/__init__.py | 0 .../widgets/button_from_svgs.py | 0 .../standalonepublish/widgets/model_asset.py | 0 .../widgets/model_filter_proxy_exact_match.py | 0 .../model_filter_proxy_recursive_sort.py | 0 .../standalonepublish/widgets/model_node.py | 0 .../widgets/model_tasks_template.py | 0 .../standalonepublish/widgets/model_tree.py | 0 .../widgets/model_tree_view_deselectable.py | 0 .../standalonepublish/widgets/widget_asset.py | 0 .../widgets/widget_component_item.py | 0 .../widgets/widget_components.py | 0 .../widgets/widget_components_list.py | 0 .../widgets/widget_drop_empty.py | 0 .../widgets/widget_drop_frame.py | 0 .../widgets/widget_family.py | 0 .../widgets/widget_family_desc.py | 0 .../widgets/widget_shadow.py | 0 49 files changed, 4698 insertions(+), 21 deletions(-) create mode 100644 pype/tools/config_setting/widgets/base0.py create mode 100644 pype/tools/config_setting/widgets/inputs0.py create mode 100644 pype/tools/config_setting/widgets/inputs1.py create mode 100644 pype/tools/standalonepublish/__init__.py rename pype/{modules => tools}/standalonepublish/__main__.py (100%) rename pype/{modules => tools}/standalonepublish/app.py (100%) rename pype/{modules => tools}/standalonepublish/publish.py (100%) rename pype/{modules => tools}/standalonepublish/resources/__init__.py (100%) rename pype/{modules => tools}/standalonepublish/resources/edit.svg (100%) rename pype/{modules => tools}/standalonepublish/resources/file.png (100%) rename pype/{modules => tools}/standalonepublish/resources/files.png (100%) rename pype/{modules => tools}/standalonepublish/resources/houdini.png (100%) rename pype/{modules => tools}/standalonepublish/resources/image_file.png (100%) rename pype/{modules => tools}/standalonepublish/resources/image_files.png (100%) rename pype/{modules => tools}/standalonepublish/resources/information.svg (100%) rename pype/{modules => tools}/standalonepublish/resources/maya.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_disabled.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_pressed.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_pressed_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/nuke.png (100%) rename pype/{modules => tools}/standalonepublish/resources/premiere.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_disabled.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_pressed.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_pressed_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/video_file.png (100%) rename pype/{modules => tools}/standalonepublish/widgets/__init__.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/button_from_svgs.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_asset.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_filter_proxy_exact_match.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_filter_proxy_recursive_sort.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_node.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_tasks_template.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_tree.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_tree_view_deselectable.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_asset.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_component_item.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_components.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_components_list.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_drop_empty.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_drop_frame.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_family.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_family_desc.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_shadow.py (100%) diff --git a/pype/modules/standalonepublish/__init__.py b/pype/modules/standalonepublish/__init__.py index 8e615afbea..4038b696d9 100644 --- a/pype/modules/standalonepublish/__init__.py +++ b/pype/modules/standalonepublish/__init__.py @@ -1,14 +1,5 @@ -PUBLISH_PATHS = [] - from .standalonepublish_module import StandAlonePublishModule -from .app import ( - show, - cli -) -__all__ = [ - "show", - "cli" -] + def tray_init(tray_widget, main_widget): return StandAlonePublishModule(main_widget, tray_widget) diff --git a/pype/modules/standalonepublish/standalonepublish_module.py b/pype/modules/standalonepublish/standalonepublish_module.py index 64195bc271..b528642e8d 100644 --- a/pype/modules/standalonepublish/standalonepublish_module.py +++ b/pype/modules/standalonepublish/standalonepublish_module.py @@ -1,21 +1,19 @@ import os -from .app import show -from .widgets import QtWidgets import pype -from . import PUBLISH_PATHS class StandAlonePublishModule: - def __init__(self, main_parent=None, parent=None): self.main_parent = main_parent self.parent_widget = parent - PUBLISH_PATHS.clear() - PUBLISH_PATHS.append(os.path.sep.join( - [pype.PLUGINS_DIR, "standalonepublisher", "publish"] - )) + self.publish_paths = [ + os.path.join( + pype.PLUGINS_DIR, "standalonepublisher", "publish" + ) + ] def tray_menu(self, parent_menu): + from Qt import QtWidgets self.run_action = QtWidgets.QAction( "Publish", parent_menu ) @@ -24,9 +22,9 @@ class StandAlonePublishModule: def process_modules(self, modules): if "FtrackModule" in modules: - PUBLISH_PATHS.append(os.path.sep.join( - [pype.PLUGINS_DIR, "ftrack", "publish"] + self.publish_paths.append(os.path.join( + pype.PLUGINS_DIR, "ftrack", "publish" )) def show(self): - show(self.main_parent, False) + print("Running") diff --git a/pype/tools/config_setting/widgets/base0.py b/pype/tools/config_setting/widgets/base0.py new file mode 100644 index 0000000000..7f01f27ca8 --- /dev/null +++ b/pype/tools/config_setting/widgets/base0.py @@ -0,0 +1,404 @@ +import os +import json +import copy +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .widgets import UnsavedChangesDialog +from .lib import NOT_SET +from avalon import io +from queue import Queue + + +class TypeToKlass: + types = {} + + +class PypeConfigurationWidget: + default_state = "" + + def config_value(self): + raise NotImplementedError( + "Method `config_value` is not implemented for `{}`.".format( + self.__class__.__name__ + ) + ) + + def value_from_values(self, values, keys=None): + if not values: + return NOT_SET + + if keys is None: + keys = self.keys + + value = values + for key in keys: + if not isinstance(value, dict): + raise TypeError( + "Expected dictionary got {}.".format(str(type(value))) + ) + + if key not in value: + return NOT_SET + value = value[key] + return value + + def style_state(self, is_overriden, is_modified): + items = [] + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or self.default_state + + def add_children_gui(self, child_configuration, values): + raise NotImplementedError(( + "Method `add_children_gui` is not implemented for `{}`." + ).format(self.__class__.__name__)) + + +class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): + is_overidable = False + is_overriden = False + is_group = False + any_parent_is_group = False + ignore_value_changes = False + + def __init__(self, parent=None): + super(StudioWidget, self).__init__(parent) + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + # scroll_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + # scroll_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + save_btn = QtWidgets.QPushButton("Save") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(save_btn, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(scroll_widget, 1) + layout.addWidget(footer_widget, 0) + + save_btn.clicked.connect(self._save) + + self.reset() + + def reset(self): + if self.content_layout.count() != 0: + for widget in self.input_fields: + self.content_layout.removeWidget(widget) + widget.deleteLater() + self.input_fields.clear() + + values = {"studio": config.studio_presets()} + schema = config.gui_schema("studio_schema", "studio_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + self.schema = schema + + def _save(self): + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["studio"] + + # Load studio data with metadata + current_presets = config.studio_presets() + + keys_to_file = config.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + origin_values = current_presets + for key in key_sequence: + if key not in origin_values: + origin_values = {} + break + origin_values = origin_values[key] + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + origin_values.update(new_values) + + output_path = os.path.join( + config.studio_presets_path, subpath + ) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(output_path, "w") as file_stream: + json.dump(origin_values, file_stream, indent=4) + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + +class ProjectListView(QtWidgets.QListView): + left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + index = self.indexAt(event.pos()) + self.left_mouse_released_at.emit(index) + super(ProjectListView, self).mouseReleaseEvent(event) + + +class ProjectListWidget(QtWidgets.QWidget): + default = "< Default >" + project_changed = QtCore.Signal() + + def __init__(self, parent): + self._parent = parent + + self.current_project = None + + super(ProjectListWidget, self).__init__(parent) + + label_widget = QtWidgets.QLabel("Projects") + project_list = ProjectListView(self) + project_list.setModel(QtGui.QStandardItemModel()) + + # Do not allow editing + project_list.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) + # Do not automatically handle selection + project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(3) + layout.addWidget(label_widget, 0) + layout.addWidget(project_list, 1) + + project_list.left_mouse_released_at.connect(self.on_item_clicked) + + self.project_list = project_list + + self.refresh() + + def on_item_clicked(self, new_index): + new_project_name = new_index.data(QtCore.Qt.DisplayRole) + if new_project_name is None: + return + + if self.current_project == new_project_name: + return + + save_changes = False + change_project = False + if self.validate_context_change(): + change_project = True + + else: + dialog = UnsavedChangesDialog(self) + result = dialog.exec_() + if result == 1: + save_changes = True + change_project = True + + elif result == 2: + change_project = True + + if save_changes: + self._parent._save() + + if change_project: + self.select_project(new_project_name) + self.current_project = new_project_name + self.project_changed.emit() + else: + self.select_project(self.current_project) + + def validate_context_change(self): + # TODO add check if project can be changed (is modified) + for item in self._parent.input_fields: + is_modified = item.child_modified + if is_modified: + return False + return True + + def project_name(self): + if self.current_project == self.default: + return None + return self.current_project + + def select_project(self, project_name): + model = self.project_list.model() + found_items = model.findItems(project_name) + if not found_items: + found_items = model.findItems(self.default) + + index = model.indexFromItem(found_items[0]) + self.project_list.selectionModel().clear() + self.project_list.selectionModel().setCurrentIndex( + index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent + ) + + def refresh(self): + selected_project = None + for index in self.project_list.selectedIndexes(): + selected_project = index.data(QtCore.Qt.DisplayRole) + break + + model = self.project_list.model() + model.clear() + items = [self.default] + io.install() + for project_doc in tuple(io.projects()): + items.append(project_doc["name"]) + + for item in items: + model.appendRow(QtGui.QStandardItem(item)) + + self.select_project(selected_project) + + self.current_project = self.project_list.currentIndex().data( + QtCore.Qt.DisplayRole + ) + + +class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): + is_overriden = False + is_group = False + any_parent_is_group = False + + def __init__(self, parent=None): + super(ProjectWidget, self).__init__(parent) + + self.is_overidable = False + self.ignore_value_changes = False + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + project_list_widget = ProjectListWidget(self) + content_layout.addWidget(project_list_widget) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + save_btn = QtWidgets.QPushButton("Save") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(save_btn, 0) + + presets_widget = QtWidgets.QWidget() + presets_layout = QtWidgets.QVBoxLayout(presets_widget) + presets_layout.setContentsMargins(0, 0, 0, 0) + presets_layout.setSpacing(0) + + presets_layout.addWidget(scroll_widget, 1) + presets_layout.addWidget(footer_widget, 0) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(project_list_widget, 0) + layout.addWidget(presets_widget, 1) + + save_btn.clicked.connect(self._save) + project_list_widget.project_changed.connect(self._on_project_change) + + self.project_list_widget = project_list_widget + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + self.reset() + + def reset(self): + values = config.global_project_presets() + schema = config.gui_schema("projects_schema", "project_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + def _on_project_change(self): + project_name = self.project_list_widget.project_name() + + if project_name is None: + overrides = None + self.is_overidable = False + else: + overrides = config.project_preset_overrides(project_name) + self.is_overidable = True + + self.ignore_value_changes = True + for item in self.input_fields: + item.apply_overrides(overrides) + self.ignore_value_changes = False + + def _save(self): + output = {} + for item in self.input_fields: + if hasattr(item, "override_value"): + print(item.override_value()) + else: + print("*** missing `override_value`", item) + + # for item in self.input_fields: + # output.update(item.config_value()) + # + # for key in reversed(self.keys): + # _output = {key: output} + # output = _output + + print(json.dumps(output, indent=4)) diff --git a/pype/tools/config_setting/widgets/inputs0.py b/pype/tools/config_setting/widgets/inputs0.py new file mode 100644 index 0000000000..7ab9c7fa01 --- /dev/null +++ b/pype/tools/config_setting/widgets/inputs0.py @@ -0,0 +1,2145 @@ +import json +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .base import PypeConfigurationWidget, TypeToKlass +from .widgets import ( + ClickableWidget, + ExpandingWidget, + ModifiedIntSpinBox, + ModifiedFloatSpinBox +) +from .lib import NOT_SET, AS_WIDGET + + +class SchemeGroupHierarchyBug(Exception): + def __init__(self, msg=None): + if not msg: + # TODO better message + msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" + super(SchemeGroupHierarchyBug, self).__init__(msg) + + +class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._as_widget = as_widget + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + self.override_value = None + + super(BooleanWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.checkbox = QtWidgets.QCheckBox() + self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(label_widget) + + if not self._as_widget: + self.label_widget = label_widget + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + layout.addWidget(self.checkbox) + + self.default_value = self.item_value() + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, bool): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + def set_value(self, value, *, default_value=False): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.checkbox.setChecked(value) + + if default_value: + self.default_value = self.item_value() + + self._on_value_change() + + def reset_value(self): + if self.is_overidable and self.override_value is not None: + self.set_value(self.override_value) + else: + self.set_value(self.default_value) + + def clear_value(self): + self.reset_value() + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + _value = self.item_value() + is_modified = None + if self.is_overidable: + self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value + + if is_modified is None: + is_modified = _value != self.default_value + + self._is_modified = is_modified + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + else: + property_name = "state" + + self.label_widget.setProperty(property_name, state) + self.label_widget.style().polish(self.label_widget) + self._state = state + + def item_value(self): + return self.checkbox.isChecked() + + def config_value(self): + return {self.key: self.item_value()} + + +class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = as_widget + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(IntegerWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.int_input = ModifiedIntSpinBox() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.int_input) + + if not self._as_widget: + self.label_widget = label_widget + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.default_value = self.item_value() + self.override_value = None + + self.int_input.valueChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, int): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.int_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def clear_value(self): + self.set_value(0) + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.int_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.int_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = as_widget + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(FloatWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.float_input = ModifiedFloatSpinBox() + + decimals = input_data.get("decimals", 5) + maximum = input_data.get("maximum") + minimum = input_data.get("minimum") + + self.float_input.setDecimals(decimals) + if maximum is not None: + self.float_input.setMaximum(float(maximum)) + if minimum is not None: + self.float_input.setMinimum(float(minimum)) + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.float_input) + + if not self._as_widget: + self.label_widget = label_widget + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.default_value = self.item_value() + self.override_value = None + + self.float_input.valueChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, float): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.float_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value(0) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.float_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.float_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = as_widget + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(TextSingleLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QLineEdit() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, str): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.text() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = as_widget + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(TextMultiLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QPlainTextEdit() + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, str): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + +class RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + self.is_valid = None + + def set_value(self, value, *, default_value=False): + self.setPlainText(value) + + def setPlainText(self, *args, **kwargs): + super(RawJsonInput, self).setPlainText(*args, **kwargs) + self.validate() + + def focusOutEvent(self, event): + super(RawJsonInput, self).focusOutEvent(event) + self.validate() + + def validate_value(self, value): + if isinstance(value, str) and not value: + return True + + try: + json.loads(value) + return True + except Exception: + return False + + def update_style(self, is_valid=None): + if is_valid is None: + return self.validate() + + if is_valid != self.is_valid: + self.is_valid = is_valid + if is_valid: + state = "" + else: + state = "invalid" + self.setProperty("state", state) + self.style().polish(self) + + def value(self): + return self.toPlainText() + + def validate(self): + value = self.value() + is_valid = self.validate_value(value) + self.update_style(is_valid) + + +class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = as_widget + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(RawJsonWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = RawJsonInput() + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, str): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value("") + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, parent): + super(TextListItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.text_input = QtWidgets.QLineEdit() + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.text_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.text_input.textChanged.connect(self._on_value_change) + + self.is_single = False + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.text_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + return self.text_input.text() + + +class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__(self, input_data, as_widget, parent_keys, parent): + super(TextListSubWidget, self).__init__(parent) + self.setObjectName("TextListSubWidget") + + self.as_widget = as_widget + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.add_row() + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.default_value = self.item_value() + self.override_value = None + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, (list, tuple)): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + def set_value(self, value, *, default_value=False): + for input_field in self.input_fields: + self.remove_row(input_field) + + for item_text in value: + self.add_row(text=item_text) + + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value([]) + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, text=None): + # Create new item + item_widget = TextListItem(self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set text if entered text is not None + # else (when add button clicked) trigger `_on_value_change` + if text is not None: + item_widget.text_input.setText(text) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def item_value(self): + output = [] + for item in self.input_fields: + text = item.config_value() + if text: + output.append(text) + + return output + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = as_widget + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self._is_modified = False + self.is_group = is_group + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(TextListWidget, self).__init__(parent) + self.setObjectName("TextListWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + + self.label_widget = label_widget + # keys = list(parent_keys) + # keys.append(input_data["key"]) + # self.keys = keys + + self.value_widget = TextListSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + # self.value_widget.se + self.key = input_data["key"] + layout.addWidget(self.value_widget) + self.setLayout(layout) + + self.default_value = self.item_value() + self.override_value = None + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, (list, tuple)): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def set_value(self, value, *, default_value=False): + self.value_widget.set_value(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value([]) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + if as_widget: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self._is_modified = False + self._is_overriden = False + self.is_group = is_group + + self._state = None + self._child_state = None + + super(DictExpandWidget, self).__init__(parent) + self.setObjectName("DictExpandWidget") + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label = input_data["label"] + button_toggle_text = QtWidgets.QLabel(label, parent=top_part) + button_toggle_text.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(button_toggle_text) + top_part.setLayout(layout) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget = QtWidgets.QWidget(self) + content_widget.setVisible(False) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + + main_layout.addWidget(top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.button_toggle_text = button_toggle_text + + self.content_widget = content_widget + self.content_layout = content_layout + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + self.input_fields = [] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def set_default_values(self, default_values): + for input_field in self.input_fields: + input_field.set_default_values(default_values) + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(DictExpandWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + # Make sure this is set to False + self._is_overriden = False + self._state = None + self._child_state = None + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + # TODO update items + if item is not None: + for _item in self.input_fields: + if _item is not item: + _item.update_style() + + self.value_changed.emit(self) + + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_state = self.style_state(self.child_overriden, child_modified) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + self.button_toggle_text.setProperty("state", state) + self.button_toggle_text.style().polish(self.button_toggle_text) + + self._state = state + + @property + def is_modified(self): + if self.is_group: + return self.child_modified + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + @property + def is_overidable(self): + return self._parent.is_overidable + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + +class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + # TODO is not overridable by itself + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self._is_overriden = False + self.is_modified = False + self.is_group = is_group + + super(DictInvisible, self).__init__(parent) + self.setObjectName("DictInvisible") + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.input_fields = [] + + self.key = input_data["key"] + self.keys = list(parent_keys) + self.keys.append(self.key) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def set_default_values(self, default_values): + for input_field in self.input_fields: + input_field.set_default_values(default_values) + + def update_style(self, *args, **kwargs): + return + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + if item_type == "schema": + for _schema in child_configuration["children"]: + children = config.gui_schema(_schema) + self.add_children_gui(children, values) + return + + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.layout().addWidget(item) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + # TODO update items + if item is not None: + is_overriden = self.is_overriden + for _item in self.input_fields: + if _item is not item: + _item.update_style(is_overriden) + + self.value_changed.emit(self) + + def apply_overrides(self, override_value): + self._is_overriden = False + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + +class DictFormWidget(QtWidgets.QWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + + self.is_modified = False + self.is_overriden = False + self.is_group = False + + super(DictFormWidget, self).__init__(parent) + + self.input_fields = {} + self.content_layout = QtWidgets.QFormLayout(self) + + self.keys = list(parent_keys) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def set_default_values(self, default_values): + for key, input_field in self.input_fields.items(): + input_field.set_default_values(default_values) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self.value_changed.emit(self) + + def item_value(self): + output = {} + for input_field in self.input_fields.values(): + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + @property + def child_modified(self): + for input_field in self.input_fields.values(): + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields.values(): + if input_field.child_overriden: + return True + return False + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def config_value(self): + return self.item_value() + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + key = child_configuration["key"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = QtWidgets.QLabel(label) + + item = klass( + child_configuration, values, self.keys, self, label_widget + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields[key] = item + return item + + +class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, object_type, parent): + self._parent = parent + + super(ModifiableDictItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + ItemKlass = TypeToKlass.types[object_type] + + self.key_input = QtWidgets.QLineEdit() + self.key_input.setObjectName("DictKey") + + self.value_input = ItemKlass( + {}, + AS_WIDGET, + [], + self, + None + ) + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.key_input, 0) + layout.addWidget(self.value_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.key_input.textChanged.connect(self._on_value_change) + self.value_input.value_changed.connect(self._on_value_change) + + self.default_key = self._key() + self.default_value = self.value_input.item_value() + + self.override_key = None + self.override_value = None + + self.is_single = False + + def _key(self): + return self.key_input.text() + + def _on_value_change(self, item=None): + self.update_style() + self.value_changed.emit(self) + + @property + def is_group(self): + return self._parent.is_group + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def is_key_modified(self): + return self._key() != self.default_key + + def is_value_modified(self): + return self.value_input.is_modified + + @property + def is_modified(self): + return self.is_value_modified() or self.is_key_modified() + + def update_style(self): + if self.is_key_modified(): + state = "modified" + else: + state = "" + + self.key_input.setProperty("state", state) + self.key_input.style().polish(self.key_input) + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.value_input.clear_value() + self.key_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + if not key: + return {} + return {key: value} + + +class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__(self, input_data, as_widget, parent_keys, parent): + self._parent = parent + + super(ModifiableDictSubWidget, self).__init__(parent) + self.setObjectName("ModifiableDictSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.object_type = input_data["object_type"] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + if self.count() == 0: + self.add_row() + + self.default_value = self.config_value() + self.override_value = None + + def set_default_values(self, default_values): + for input_field in self.input_fields: + self.remove_row(input_field) + + value = self.value_from_values(default_values) + if value is NOT_SET: + self.defaul_value = value + return + + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_value) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._parent.is_overriden + + @property + def is_group(self): + return self._parent.is_group + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, key=None, value=None): + # Create new item + item_widget = ModifiableDictItem(self.object_type, self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set value if entered value is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None and key is not None: + item_widget.default_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, default_value=True) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def config_value(self): + output = {} + for item in self.input_fields: + item_value = item.config_value() + if item_value: + output.update(item_value) + return output + + +class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.any_parent_is_group = any_parent_is_group + + self.is_group = is_group + self._is_modified = False + self._is_overriden = False + self._was_overriden = False + self._state = None + + super(ModifiableDict, self).__init__(input_data["label"], parent) + self.setObjectName("ModifiableDict") + + self.value_widget = ModifiableDictSubWidget( + input_data, as_widget, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + self.set_content_widget(self.value_widget) + + self.key = input_data["key"] + + self.default_value = self.item_value() + self.override_value = None + + def set_default_values(self, default_values): + self.value_widget.set_default_values(default_values) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + + if self.is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.default_value + + self.value_changed.emit(self) + + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def is_modified(self): + return self._is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def is_modified(self): + return self._is_modified + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + self._state = None + self._is_modified = False + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.setProperty("state", child_state) + self.style().polish(self) + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +TypeToKlass.types["boolean"] = BooleanWidget +TypeToKlass.types["text-singleline"] = TextSingleLineWidget +TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["raw-json"] = RawJsonWidget +TypeToKlass.types["int"] = IntegerWidget +TypeToKlass.types["float"] = FloatWidget +TypeToKlass.types["dict-expanding"] = DictExpandWidget +TypeToKlass.types["dict-form"] = DictFormWidget +TypeToKlass.types["dict-invisible"] = DictInvisible +TypeToKlass.types["dict-modifiable"] = ModifiableDict +TypeToKlass.types["list-text"] = TextListWidget diff --git a/pype/tools/config_setting/widgets/inputs1.py b/pype/tools/config_setting/widgets/inputs1.py new file mode 100644 index 0000000000..f9eb60f31a --- /dev/null +++ b/pype/tools/config_setting/widgets/inputs1.py @@ -0,0 +1,2131 @@ +import json +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .base import PypeConfigurationWidget, TypeToKlass +from .widgets import ( + ClickableWidget, + ExpandingWidget, + ModifiedIntSpinBox, + ModifiedFloatSpinBox +) +from .lib import NOT_SET, AS_WIDGET + + +class SchemeGroupHierarchyBug(Exception): + def __init__(self, msg=None): + if not msg: + # TODO better message + msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" + super(SchemeGroupHierarchyBug, self).__init__(msg) + + +class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._as_widget = values is AS_WIDGET + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(BooleanWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.checkbox = QtWidgets.QCheckBox() + self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(label_widget) + + layout.addWidget(self.checkbox) + + if not self._as_widget: + self.label_widget = label_widget + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.checkbox.setChecked(value) + + self.default_value = self.item_value() + self.override_value = None + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_value(self, value, *, default_value=False): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.checkbox.setChecked(value) + + if default_value: + self.default_value = self.item_value() + + self._on_value_change() + + def reset_value(self): + if self.is_overidable and self.override_value is not None: + self.set_value(self.override_value) + else: + self.set_value(self.default_value) + + def clear_value(self): + self.reset_value() + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + _value = self.item_value() + is_modified = None + if self.is_overidable: + self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value + + if is_modified is None: + is_modified = _value != self.default_value + + self._is_modified = is_modified + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + else: + property_name = "state" + + self.label_widget.setProperty(property_name, state) + self.label_widget.style().polish(self.label_widget) + self._state = state + + def item_value(self): + return self.checkbox.isChecked() + + def config_value(self): + return {self.key: self.item_value()} + + def override_value(self): + if self.is_overriden: + output = { + "is_group": self.is_group, + "value": self.config_value() + } + return output + + +class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(IntegerWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.int_input = ModifiedIntSpinBox() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.int_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.int_input.setValue(value) + + self.default_value = self.item_value() + self.override_value = None + + self.int_input.valueChanged.connect(self._on_value_change) + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.int_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def clear_value(self): + self.set_value(0) + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.int_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.int_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(FloatWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.float_input = ModifiedFloatSpinBox() + + decimals = input_data.get("decimals", 5) + maximum = input_data.get("maximum") + minimum = input_data.get("minimum") + + self.float_input.setDecimals(decimals) + if maximum is not None: + self.float_input.setMaximum(float(maximum)) + if minimum is not None: + self.float_input.setMinimum(float(minimum)) + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.float_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.float_input.setValue(value) + + self.default_value = self.item_value() + self.override_value = None + + self.float_input.valueChanged.connect(self._on_value_change) + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.float_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value(0) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.float_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.float_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(TextSingleLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QLineEdit() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setText(value) + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.text() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(TextMultiLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QPlainTextEdit() + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setPlainText(value) + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + +class RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + self.is_valid = None + + def set_value(self, value, *, default_value=False): + self.setPlainText(value) + + def setPlainText(self, *args, **kwargs): + super(RawJsonInput, self).setPlainText(*args, **kwargs) + self.validate() + + def focusOutEvent(self, event): + super(RawJsonInput, self).focusOutEvent(event) + self.validate() + + def validate_value(self, value): + if isinstance(value, str) and not value: + return True + + try: + json.loads(value) + return True + except Exception: + return False + + def update_style(self, is_valid=None): + if is_valid is None: + return self.validate() + + if is_valid != self.is_valid: + self.is_valid = is_valid + if is_valid: + state = "" + else: + state = "invalid" + self.setProperty("state", state) + self.style().polish(self) + + def value(self): + return self.toPlainText() + + def validate(self): + value = self.value() + is_valid = self.validate_value(value) + self.update_style(is_valid) + + +class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.is_group = is_group + self._is_modified = False + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(RawJsonWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = RawJsonInput() + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setPlainText(value) + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value("") + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, parent): + super(TextListItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.text_input = QtWidgets.QLineEdit() + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.text_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.text_input.textChanged.connect(self._on_value_change) + + self.is_single = False + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.text_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + return self.text_input.text() + + +class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__(self, input_data, values, parent_keys, parent): + super(TextListSubWidget, self).__init__(parent) + self.setObjectName("TextListSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.add_row() + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.set_value(value) + + self.default_value = self.item_value() + self.override_value = None + + def set_value(self, value, *, default_value=False): + for input_field in self.input_fields: + self.remove_row(input_field) + + for item_text in value: + self.add_row(text=item_text) + + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value([]) + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, text=None): + # Create new item + item_widget = TextListItem(self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set text if entered text is not None + # else (when add button clicked) trigger `_on_value_change` + if text is not None: + item_widget.text_input.setText(text) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def item_value(self): + output = [] + for item in self.input_fields: + text = item.config_value() + if text: + output.append(text) + + return output + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self._is_modified = False + self.is_group = is_group + self._was_overriden = False + self._is_overriden = False + + self._state = None + + super(TextListWidget, self).__init__(parent) + self.setObjectName("TextListWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + + self.label_widget = label_widget + # keys = list(parent_keys) + # keys.append(input_data["key"]) + # self.keys = keys + + self.value_widget = TextListSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + # self.value_widget.se + self.key = input_data["key"] + layout.addWidget(self.value_widget) + self.setLayout(layout) + + self.default_value = self.item_value() + self.override_value = None + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def set_value(self, value, *, default_value=False): + self.value_widget.set_value(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value([]) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + if values is AS_WIDGET: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self._is_modified = False + self._is_overriden = False + self.is_group = is_group + + self._state = None + self._child_state = None + + super(DictExpandWidget, self).__init__(parent) + self.setObjectName("DictExpandWidget") + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label = input_data["label"] + button_toggle_text = QtWidgets.QLabel(label, parent=top_part) + button_toggle_text.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(button_toggle_text) + top_part.setLayout(layout) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget = QtWidgets.QWidget(self) + content_widget.setVisible(False) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + + main_layout.addWidget(top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.button_toggle_text = button_toggle_text + + self.content_widget = content_widget + self.content_layout = content_layout + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + self.input_fields = [] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(DictExpandWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + # Make sure this is set to False + self._is_overriden = False + self._state = None + self._child_state = None + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + # TODO update items + if item is not None: + for _item in self.input_fields: + if _item is not item: + _item.update_style() + + self.value_changed.emit(self) + + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_state = self.style_state(self.child_overriden, child_modified) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + self.button_toggle_text.setProperty("state", state) + self.button_toggle_text.style().polish(self.button_toggle_text) + + self._state = state + + @property + def is_modified(self): + if self.is_group: + return self.child_modified + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + @property + def is_overidable(self): + return self._parent.is_overidable + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + def override_values(self): + if not self.is_overriden and not self.child_overriden: + return + + value = {} + for item in self.input_fields: + if hasattr(item, "override_values"): + print("*** HAVE `override_values`", item) + print(item.override_values()) + else: + print("*** missing `override_values`", item) + + if not value: + return + + output = { + "is_group": self.is_group, + "value": value + } + return output + + +class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + # TODO is not overridable by itself + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self._is_overriden = False + self.is_modified = False + self.is_group = is_group + + super(DictInvisible, self).__init__(parent) + self.setObjectName("DictInvisible") + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.input_fields = [] + + if "key" not in input_data: + print(json.dumps(input_data, indent=4)) + + self.key = input_data["key"] + self.keys = list(parent_keys) + self.keys.append(self.key) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def update_style(self, *args, **kwargs): + return + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + if item_type == "schema": + for _schema in child_configuration["children"]: + children = config.gui_schema(_schema) + self.add_children_gui(children, values) + return + + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.layout().addWidget(item) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + # TODO update items + if item is not None: + is_overriden = self.is_overriden + for _item in self.input_fields: + if _item is not item: + _item.update_style(is_overriden) + + self.value_changed.emit(self) + + def apply_overrides(self, override_value): + self._is_overriden = False + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def override_values(self): + if not self.is_overriden and not self.child_overriden: + return + + value = {} + for item in self.input_fields: + if hasattr(item, "override_values"): + print("*** HAVE `override_values`", item) + print(item.override_values()) + else: + print("*** missing `override_values`", item) + + if not value: + return + + output = { + "is_group": self.is_group, + "value": value + } + return output + + +class DictFormWidget(QtWidgets.QWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + + self.is_modified = False + self.is_overriden = False + self.is_group = False + + super(DictFormWidget, self).__init__(parent) + + self.input_fields = {} + self.content_layout = QtWidgets.QFormLayout(self) + + self.keys = list(parent_keys) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self.value_changed.emit(self) + + def item_value(self): + output = {} + for input_field in self.input_fields.values(): + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + @property + def child_modified(self): + for input_field in self.input_fields.values(): + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields.values(): + if input_field.child_overriden: + return True + return False + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def config_value(self): + return self.item_value() + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + key = child_configuration["key"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = QtWidgets.QLabel(label) + + item = klass( + child_configuration, values, self.keys, self, label_widget + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields[key] = item + return item + + +class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, object_type, parent): + self._parent = parent + + super(ModifiableDictItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + ItemKlass = TypeToKlass.types[object_type] + + self.key_input = QtWidgets.QLineEdit() + self.key_input.setObjectName("DictKey") + + self.value_input = ItemKlass( + {}, + AS_WIDGET, + [], + self, + None + ) + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.key_input, 0) + layout.addWidget(self.value_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.key_input.textChanged.connect(self._on_value_change) + self.value_input.value_changed.connect(self._on_value_change) + + self.default_key = self._key() + self.default_value = self.value_input.item_value() + + self.override_key = None + self.override_value = None + + self.is_single = False + + def _key(self): + return self.key_input.text() + + def _on_value_change(self, item=None): + self.update_style() + self.value_changed.emit(self) + + @property + def is_group(self): + return self._parent.is_group + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def is_key_modified(self): + return self._key() != self.default_key + + def is_value_modified(self): + return self.value_input.is_modified + + @property + def is_modified(self): + return self.is_value_modified() or self.is_key_modified() + + def update_style(self): + if self.is_key_modified(): + state = "modified" + else: + state = "" + + self.key_input.setProperty("state", state) + self.key_input.style().polish(self.key_input) + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.value_input.clear_value() + self.key_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + if not key: + return {} + return {key: value} + + +class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__(self, input_data, values, parent_keys, parent): + self._parent = parent + + super(ModifiableDictSubWidget, self).__init__(parent) + self.setObjectName("ModifiableDictSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.object_type = input_data["object_type"] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_value) + + if self.count() == 0: + self.add_row() + + self.default_value = self.config_value() + self.override_value = None + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._parent.is_overriden + + @property + def is_group(self): + return self._parent.is_group + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, key=None, value=None): + # Create new item + item_widget = ModifiableDictItem(self.object_type, self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set value if entered value is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None and key is not None: + item_widget.default_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, default_value=True) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def config_value(self): + output = {} + for item in self.input_fields: + item_value = item.config_value() + if item_value: + output.update(item_value) + return output + + +class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, + label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + if not any_parent_is_group and not is_group: + is_group = True + + self.any_parent_is_group = any_parent_is_group + + self.is_group = is_group + self._is_modified = False + self._is_overriden = False + self._was_overriden = False + self._state = None + + super(ModifiableDict, self).__init__(input_data["label"], parent) + self.setObjectName("ModifiableDict") + + self.value_widget = ModifiableDictSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + self.set_content_widget(self.value_widget) + + self.key = input_data["key"] + + self.default_value = self.item_value() + self.override_value = None + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + + if self.is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.default_value + + self.value_changed.emit(self) + + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def is_modified(self): + return self._is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def is_modified(self): + return self._is_modified + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + self._state = None + self._is_modified = False + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.setProperty("state", child_state) + self.style().polish(self) + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + def override_value(self): + return + + +TypeToKlass.types["boolean"] = BooleanWidget +TypeToKlass.types["text-singleline"] = TextSingleLineWidget +TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["raw-json"] = RawJsonWidget +TypeToKlass.types["int"] = IntegerWidget +TypeToKlass.types["float"] = FloatWidget +TypeToKlass.types["dict-expanding"] = DictExpandWidget +TypeToKlass.types["dict-form"] = DictFormWidget +TypeToKlass.types["dict-invisible"] = DictInvisible +TypeToKlass.types["dict-modifiable"] = ModifiableDict +TypeToKlass.types["list-text"] = TextListWidget diff --git a/pype/tools/standalonepublish/__init__.py b/pype/tools/standalonepublish/__init__.py new file mode 100644 index 0000000000..29a4e52904 --- /dev/null +++ b/pype/tools/standalonepublish/__init__.py @@ -0,0 +1,8 @@ +from .app import ( + show, + cli +) +__all__ = [ + "show", + "cli" +] diff --git a/pype/modules/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py similarity index 100% rename from pype/modules/standalonepublish/__main__.py rename to pype/tools/standalonepublish/__main__.py diff --git a/pype/modules/standalonepublish/app.py b/pype/tools/standalonepublish/app.py similarity index 100% rename from pype/modules/standalonepublish/app.py rename to pype/tools/standalonepublish/app.py diff --git a/pype/modules/standalonepublish/publish.py b/pype/tools/standalonepublish/publish.py similarity index 100% rename from pype/modules/standalonepublish/publish.py rename to pype/tools/standalonepublish/publish.py diff --git a/pype/modules/standalonepublish/resources/__init__.py b/pype/tools/standalonepublish/resources/__init__.py similarity index 100% rename from pype/modules/standalonepublish/resources/__init__.py rename to pype/tools/standalonepublish/resources/__init__.py diff --git a/pype/modules/standalonepublish/resources/edit.svg b/pype/tools/standalonepublish/resources/edit.svg similarity index 100% rename from pype/modules/standalonepublish/resources/edit.svg rename to pype/tools/standalonepublish/resources/edit.svg diff --git a/pype/modules/standalonepublish/resources/file.png b/pype/tools/standalonepublish/resources/file.png similarity index 100% rename from pype/modules/standalonepublish/resources/file.png rename to pype/tools/standalonepublish/resources/file.png diff --git a/pype/modules/standalonepublish/resources/files.png b/pype/tools/standalonepublish/resources/files.png similarity index 100% rename from pype/modules/standalonepublish/resources/files.png rename to pype/tools/standalonepublish/resources/files.png diff --git a/pype/modules/standalonepublish/resources/houdini.png b/pype/tools/standalonepublish/resources/houdini.png similarity index 100% rename from pype/modules/standalonepublish/resources/houdini.png rename to pype/tools/standalonepublish/resources/houdini.png diff --git a/pype/modules/standalonepublish/resources/image_file.png b/pype/tools/standalonepublish/resources/image_file.png similarity index 100% rename from pype/modules/standalonepublish/resources/image_file.png rename to pype/tools/standalonepublish/resources/image_file.png diff --git a/pype/modules/standalonepublish/resources/image_files.png b/pype/tools/standalonepublish/resources/image_files.png similarity index 100% rename from pype/modules/standalonepublish/resources/image_files.png rename to pype/tools/standalonepublish/resources/image_files.png diff --git a/pype/modules/standalonepublish/resources/information.svg b/pype/tools/standalonepublish/resources/information.svg similarity index 100% rename from pype/modules/standalonepublish/resources/information.svg rename to pype/tools/standalonepublish/resources/information.svg diff --git a/pype/modules/standalonepublish/resources/maya.png b/pype/tools/standalonepublish/resources/maya.png similarity index 100% rename from pype/modules/standalonepublish/resources/maya.png rename to pype/tools/standalonepublish/resources/maya.png diff --git a/pype/modules/standalonepublish/resources/menu.png b/pype/tools/standalonepublish/resources/menu.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu.png rename to pype/tools/standalonepublish/resources/menu.png diff --git a/pype/modules/standalonepublish/resources/menu_disabled.png b/pype/tools/standalonepublish/resources/menu_disabled.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_disabled.png rename to pype/tools/standalonepublish/resources/menu_disabled.png diff --git a/pype/modules/standalonepublish/resources/menu_hover.png b/pype/tools/standalonepublish/resources/menu_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_hover.png rename to pype/tools/standalonepublish/resources/menu_hover.png diff --git a/pype/modules/standalonepublish/resources/menu_pressed.png b/pype/tools/standalonepublish/resources/menu_pressed.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_pressed.png rename to pype/tools/standalonepublish/resources/menu_pressed.png diff --git a/pype/modules/standalonepublish/resources/menu_pressed_hover.png b/pype/tools/standalonepublish/resources/menu_pressed_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_pressed_hover.png rename to pype/tools/standalonepublish/resources/menu_pressed_hover.png diff --git a/pype/modules/standalonepublish/resources/nuke.png b/pype/tools/standalonepublish/resources/nuke.png similarity index 100% rename from pype/modules/standalonepublish/resources/nuke.png rename to pype/tools/standalonepublish/resources/nuke.png diff --git a/pype/modules/standalonepublish/resources/premiere.png b/pype/tools/standalonepublish/resources/premiere.png similarity index 100% rename from pype/modules/standalonepublish/resources/premiere.png rename to pype/tools/standalonepublish/resources/premiere.png diff --git a/pype/modules/standalonepublish/resources/trash.png b/pype/tools/standalonepublish/resources/trash.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash.png rename to pype/tools/standalonepublish/resources/trash.png diff --git a/pype/modules/standalonepublish/resources/trash_disabled.png b/pype/tools/standalonepublish/resources/trash_disabled.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_disabled.png rename to pype/tools/standalonepublish/resources/trash_disabled.png diff --git a/pype/modules/standalonepublish/resources/trash_hover.png b/pype/tools/standalonepublish/resources/trash_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_hover.png rename to pype/tools/standalonepublish/resources/trash_hover.png diff --git a/pype/modules/standalonepublish/resources/trash_pressed.png b/pype/tools/standalonepublish/resources/trash_pressed.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_pressed.png rename to pype/tools/standalonepublish/resources/trash_pressed.png diff --git a/pype/modules/standalonepublish/resources/trash_pressed_hover.png b/pype/tools/standalonepublish/resources/trash_pressed_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_pressed_hover.png rename to pype/tools/standalonepublish/resources/trash_pressed_hover.png diff --git a/pype/modules/standalonepublish/resources/video_file.png b/pype/tools/standalonepublish/resources/video_file.png similarity index 100% rename from pype/modules/standalonepublish/resources/video_file.png rename to pype/tools/standalonepublish/resources/video_file.png diff --git a/pype/modules/standalonepublish/widgets/__init__.py b/pype/tools/standalonepublish/widgets/__init__.py similarity index 100% rename from pype/modules/standalonepublish/widgets/__init__.py rename to pype/tools/standalonepublish/widgets/__init__.py diff --git a/pype/modules/standalonepublish/widgets/button_from_svgs.py b/pype/tools/standalonepublish/widgets/button_from_svgs.py similarity index 100% rename from pype/modules/standalonepublish/widgets/button_from_svgs.py rename to pype/tools/standalonepublish/widgets/button_from_svgs.py diff --git a/pype/modules/standalonepublish/widgets/model_asset.py b/pype/tools/standalonepublish/widgets/model_asset.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_asset.py rename to pype/tools/standalonepublish/widgets/model_asset.py diff --git a/pype/modules/standalonepublish/widgets/model_filter_proxy_exact_match.py b/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_filter_proxy_exact_match.py rename to pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py diff --git a/pype/modules/standalonepublish/widgets/model_filter_proxy_recursive_sort.py b/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_filter_proxy_recursive_sort.py rename to pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py diff --git a/pype/modules/standalonepublish/widgets/model_node.py b/pype/tools/standalonepublish/widgets/model_node.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_node.py rename to pype/tools/standalonepublish/widgets/model_node.py diff --git a/pype/modules/standalonepublish/widgets/model_tasks_template.py b/pype/tools/standalonepublish/widgets/model_tasks_template.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_tasks_template.py rename to pype/tools/standalonepublish/widgets/model_tasks_template.py diff --git a/pype/modules/standalonepublish/widgets/model_tree.py b/pype/tools/standalonepublish/widgets/model_tree.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_tree.py rename to pype/tools/standalonepublish/widgets/model_tree.py diff --git a/pype/modules/standalonepublish/widgets/model_tree_view_deselectable.py b/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_tree_view_deselectable.py rename to pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py diff --git a/pype/modules/standalonepublish/widgets/widget_asset.py b/pype/tools/standalonepublish/widgets/widget_asset.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_asset.py rename to pype/tools/standalonepublish/widgets/widget_asset.py diff --git a/pype/modules/standalonepublish/widgets/widget_component_item.py b/pype/tools/standalonepublish/widgets/widget_component_item.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_component_item.py rename to pype/tools/standalonepublish/widgets/widget_component_item.py diff --git a/pype/modules/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_components.py rename to pype/tools/standalonepublish/widgets/widget_components.py diff --git a/pype/modules/standalonepublish/widgets/widget_components_list.py b/pype/tools/standalonepublish/widgets/widget_components_list.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_components_list.py rename to pype/tools/standalonepublish/widgets/widget_components_list.py diff --git a/pype/modules/standalonepublish/widgets/widget_drop_empty.py b/pype/tools/standalonepublish/widgets/widget_drop_empty.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_drop_empty.py rename to pype/tools/standalonepublish/widgets/widget_drop_empty.py diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/tools/standalonepublish/widgets/widget_drop_frame.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_drop_frame.py rename to pype/tools/standalonepublish/widgets/widget_drop_frame.py diff --git a/pype/modules/standalonepublish/widgets/widget_family.py b/pype/tools/standalonepublish/widgets/widget_family.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_family.py rename to pype/tools/standalonepublish/widgets/widget_family.py diff --git a/pype/modules/standalonepublish/widgets/widget_family_desc.py b/pype/tools/standalonepublish/widgets/widget_family_desc.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_family_desc.py rename to pype/tools/standalonepublish/widgets/widget_family_desc.py diff --git a/pype/modules/standalonepublish/widgets/widget_shadow.py b/pype/tools/standalonepublish/widgets/widget_shadow.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_shadow.py rename to pype/tools/standalonepublish/widgets/widget_shadow.py From ce70f9ed4973fb077771c8201979b2a02d24f871 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 18:02:17 +0200 Subject: [PATCH 382/947] cleaned (a little bit) app.py --- pype/tools/standalonepublish/app.py | 56 ++++++++--------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/pype/tools/standalonepublish/app.py b/pype/tools/standalonepublish/app.py index 60274f6b0a..ca6d26c025 100644 --- a/pype/tools/standalonepublish/app.py +++ b/pype/tools/standalonepublish/app.py @@ -1,18 +1,9 @@ -import os -import sys -import json -from subprocess import Popen from bson.objectid import ObjectId -from pype import lib as pypelib -from avalon.vendor.Qt import QtWidgets, QtCore -from avalon import api, style, schema -from avalon.tools import lib as parentlib -from .widgets import * -# Move this to pype lib? +from Qt import QtWidgets, QtCore +from avalon import style +from .widgets import AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget from avalon.tools.libraryloader.io_nonsingleton import DbConnector -module = sys.modules[__name__] -module.window = None class Window(QtWidgets.QDialog): """Main window of Standalone publisher. @@ -99,8 +90,14 @@ class Window(QtWidgets.QDialog): def resizeEvent(self, event=None): ''' Helps resize shadow widget ''' - position_x = (self.frameGeometry().width()-self.shadow_widget.frameGeometry().width())/2 - position_y = (self.frameGeometry().height()-self.shadow_widget.frameGeometry().height())/2 + position_x = ( + self.frameGeometry().width() + - self.shadow_widget.frameGeometry().width() + ) / 2 + position_y = ( + self.frameGeometry().height() + - self.shadow_widget.frameGeometry().height() + ) / 2 self.shadow_widget.move(position_x, position_y) w = self.frameGeometry().width() h = self.frameGeometry().height() @@ -144,7 +141,10 @@ class Window(QtWidgets.QDialog): - files/folders in clipboard (tested only on Windows OS) - copied path of file/folder in clipboard ('c:/path/to/folder') ''' - if event.key() == QtCore.Qt.Key_V and event.modifiers() == QtCore.Qt.ControlModifier: + if ( + event.key() == QtCore.Qt.Key_V + and event.modifiers() == QtCore.Qt.ControlModifier + ): clip = QtWidgets.QApplication.clipboard() self.widget_components.process_mime_data(clip) super().keyPressEvent(event) @@ -190,29 +190,3 @@ class Window(QtWidgets.QDialog): data.update(self.widget_components.collect_data()) return data - -def show(parent=None, debug=False): - try: - module.window.close() - del module.window - except (RuntimeError, AttributeError): - pass - - with parentlib.application(): - window = Window(parent) - window.show() - - module.window = window - - -def cli(args): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("project") - parser.add_argument("asset") - - args = parser.parse_args(args) - # project = args.project - # asset = args.asset - - show() From cd7b5983615939db4e551e1185113635ad99ab7e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 18:21:54 +0200 Subject: [PATCH 383/947] fixed minor issues --- .../standalonepublish_module.py | 9 ++++++- pype/tools/standalonepublish/__main__.py | 26 ++++++++++++++++--- pype/tools/standalonepublish/app.py | 4 +-- .../standalonepublish/widgets/__init__.py | 2 +- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/pype/modules/standalonepublish/standalonepublish_module.py b/pype/modules/standalonepublish/standalonepublish_module.py index b528642e8d..3ceb93578d 100644 --- a/pype/modules/standalonepublish/standalonepublish_module.py +++ b/pype/modules/standalonepublish/standalonepublish_module.py @@ -1,5 +1,8 @@ import os +import sys +import subprocess import pype +from pype import tools class StandAlonePublishModule: @@ -27,4 +30,8 @@ class StandAlonePublishModule: )) def show(self): - print("Running") + standalone_publisher_tool_path = os.path.join( + os.path.dirname(tools.__file__), + "standalonepublish" + ) + subprocess.Popen([sys.executable, standalone_publisher_tool_path]) diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py index d77bc585c5..ea6291ec18 100644 --- a/pype/tools/standalonepublish/__main__.py +++ b/pype/tools/standalonepublish/__main__.py @@ -1,5 +1,23 @@ -from . import cli +import sys +import app +import signal +from Qt import QtWidgets +from avalon import style -if __name__ == '__main__': - import sys - sys.exit(cli(sys.argv[1:])) + +if __name__ == "__main__": + qt_app = QtWidgets.QApplication(sys.argv[1:]) + # app.setQuitOnLastWindowClosed(False) + qt_app.setStyleSheet(style.load_stylesheet()) + + def signal_handler(sig, frame): + print("You pressed Ctrl+C. Process ended.") + qt_app.quit() + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + window = app.Window() + window.show() + + sys.exit(qt_app.exec_()) diff --git a/pype/tools/standalonepublish/app.py b/pype/tools/standalonepublish/app.py index ca6d26c025..8c854d9406 100644 --- a/pype/tools/standalonepublish/app.py +++ b/pype/tools/standalonepublish/app.py @@ -1,7 +1,6 @@ from bson.objectid import ObjectId from Qt import QtWidgets, QtCore -from avalon import style -from .widgets import AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget +from widgets import AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget from avalon.tools.libraryloader.io_nonsingleton import DbConnector @@ -26,7 +25,6 @@ class Window(QtWidgets.QDialog): self.setWindowTitle("Standalone Publish") self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setStyleSheet(style.load_stylesheet()) # Validators self.valid_parent = False diff --git a/pype/tools/standalonepublish/widgets/__init__.py b/pype/tools/standalonepublish/widgets/__init__.py index 9a71e0dee6..0db3643cf3 100644 --- a/pype/tools/standalonepublish/widgets/__init__.py +++ b/pype/tools/standalonepublish/widgets/__init__.py @@ -8,7 +8,7 @@ ExistsRole = QtCore.Qt.UserRole + 4 PluginRole = QtCore.Qt.UserRole + 5 PluginKeyRole = QtCore.Qt.UserRole + 6 -from ..resources import get_resource +from pype.resources import get_resource from .button_from_svgs import SvgResizable, SvgButton from .model_node import Node From e1ef2aad244444c65578f8ee038925adc177738d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 18:41:36 +0200 Subject: [PATCH 384/947] post standalone paths to process --- .../modules/standalonepublish/standalonepublish_module.py | 6 +++++- pype/tools/standalonepublish/__main__.py | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pype/modules/standalonepublish/standalonepublish_module.py b/pype/modules/standalonepublish/standalonepublish_module.py index 3ceb93578d..ed997bfd9f 100644 --- a/pype/modules/standalonepublish/standalonepublish_module.py +++ b/pype/modules/standalonepublish/standalonepublish_module.py @@ -34,4 +34,8 @@ class StandAlonePublishModule: os.path.dirname(tools.__file__), "standalonepublish" ) - subprocess.Popen([sys.executable, standalone_publisher_tool_path]) + subprocess.Popen([ + sys.executable, + standalone_publisher_tool_path, + os.pathsep.join(self.publish_paths).replace("\\", "/") + ]) diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py index ea6291ec18..21c0635cf6 100644 --- a/pype/tools/standalonepublish/__main__.py +++ b/pype/tools/standalonepublish/__main__.py @@ -1,12 +1,16 @@ +import os import sys import app import signal from Qt import QtWidgets from avalon import style +import pype +import pyblish.api if __name__ == "__main__": - qt_app = QtWidgets.QApplication(sys.argv[1:]) + pype.install() + qt_app = QtWidgets.QApplication([]) # app.setQuitOnLastWindowClosed(False) qt_app.setStyleSheet(style.load_stylesheet()) @@ -17,6 +21,8 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) + for path in sys.argv[-1].split(os.pathsep): + pyblish.api.register_plugin_path(path) window = app.Window() window.show() From 5ef59976341d8c33b31a1c9a3af806fda52080ca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 18:57:00 +0200 Subject: [PATCH 385/947] now its working --- pype/tools/standalonepublish/__main__.py | 5 +- pype/tools/standalonepublish/app.py | 4 +- pype/tools/standalonepublish/publish.py | 98 ---------------- .../widgets/widget_components.py | 109 +++++++++++++++++- 4 files changed, 107 insertions(+), 109 deletions(-) diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py index 21c0635cf6..5bcf514994 100644 --- a/pype/tools/standalonepublish/__main__.py +++ b/pype/tools/standalonepublish/__main__.py @@ -9,7 +9,6 @@ import pyblish.api if __name__ == "__main__": - pype.install() qt_app = QtWidgets.QApplication([]) # app.setQuitOnLastWindowClosed(False) qt_app.setStyleSheet(style.load_stylesheet()) @@ -21,9 +20,7 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) - for path in sys.argv[-1].split(os.pathsep): - pyblish.api.register_plugin_path(path) - window = app.Window() + window = app.Window(sys.argv[-1].split(os.pathsep)) window.show() sys.exit(qt_app.exec_()) diff --git a/pype/tools/standalonepublish/app.py b/pype/tools/standalonepublish/app.py index 8c854d9406..d139366a1c 100644 --- a/pype/tools/standalonepublish/app.py +++ b/pype/tools/standalonepublish/app.py @@ -18,10 +18,12 @@ class Window(QtWidgets.QDialog): WIDTH = 1100 HEIGHT = 500 - def __init__(self, parent=None): + def __init__(self, pyblish_paths, parent=None): super(Window, self).__init__(parent=parent) self._db.install() + self.pyblish_paths = pyblish_paths + self.setWindowTitle("Standalone Publish") self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) diff --git a/pype/tools/standalonepublish/publish.py b/pype/tools/standalonepublish/publish.py index 27062e8457..a4bb81ad3c 100644 --- a/pype/tools/standalonepublish/publish.py +++ b/pype/tools/standalonepublish/publish.py @@ -1,108 +1,10 @@ import os import sys -import json -import tempfile -import random -import string -from avalon import io import pype -from pype.api import execute, Logger - import pyblish.api -log = Logger().get_logger("standalonepublisher") - - -def set_context(project, asset, task, app): - ''' Sets context for pyblish (must be done before pyblish is launched) - :param project: Name of `Project` where instance should be published - :type project: str - :param asset: Name of `Asset` where instance should be published - :type asset: str - ''' - os.environ["AVALON_PROJECT"] = project - io.Session["AVALON_PROJECT"] = project - os.environ["AVALON_ASSET"] = asset - io.Session["AVALON_ASSET"] = asset - if not task: - task = '' - os.environ["AVALON_TASK"] = task - io.Session["AVALON_TASK"] = task - - io.install() - - av_project = io.find_one({'type': 'project'}) - av_asset = io.find_one({ - "type": 'asset', - "name": asset - }) - - parents = av_asset['data']['parents'] - hierarchy = '' - if parents and len(parents) > 0: - hierarchy = os.path.sep.join(parents) - - os.environ["AVALON_HIERARCHY"] = hierarchy - io.Session["AVALON_HIERARCHY"] = hierarchy - - os.environ["AVALON_PROJECTCODE"] = av_project['data'].get('code', '') - io.Session["AVALON_PROJECTCODE"] = av_project['data'].get('code', '') - - io.Session["current_dir"] = os.path.normpath(os.getcwd()) - - os.environ["AVALON_APP"] = app - io.Session["AVALON_APP"] = app - - io.uninstall() - - -def publish(data, gui=True): - # cli pyblish seems like better solution - return cli_publish(data, gui) - - -def cli_publish(data, gui=True): - from . import PUBLISH_PATHS - - PUBLISH_SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "publish.py") - io.install() - - # Create hash name folder in temp - chars = "".join([random.choice(string.ascii_letters) for i in range(15)]) - staging_dir = tempfile.mkdtemp(chars) - - # create also json and fill with data - json_data_path = staging_dir + os.path.basename(staging_dir) + '.json' - with open(json_data_path, 'w') as outfile: - json.dump(data, outfile) - - envcopy = os.environ.copy() - envcopy["PYBLISH_HOSTS"] = "standalonepublisher" - envcopy["SAPUBLISH_INPATH"] = json_data_path - envcopy["PYBLISHGUI"] = "pyblish_pype" - envcopy["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) - if data.get("family", "").lower() == "editorial": - envcopy["PYBLISH_SUSPEND_LOGS"] = "1" - - result = execute( - [sys.executable, PUBLISH_SCRIPT_PATH], - env=envcopy - ) - - result = {} - if os.path.exists(json_data_path): - with open(json_data_path, "r") as f: - result = json.load(f) - - log.info(f"Publish result: {result}") - - io.uninstall() - - return False - - def main(env): from avalon.tools import publish # Registers pype's Global pyblish plugins diff --git a/pype/tools/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py index 90167f2fa6..3b6c326af0 100644 --- a/pype/tools/standalonepublish/widgets/widget_components.py +++ b/pype/tools/standalonepublish/widgets/widget_components.py @@ -1,7 +1,17 @@ -from . import QtWidgets, QtCore, QtGui -from . import DropDataFrame +import os +import sys +import json +import tempfile +import random +import string -from .. import publish +from Qt import QtWidgets, QtCore +from . import DropDataFrame +from avalon.tools import publish +from avalon import io +from pype.api import execute, Logger + +log = Logger().get_logger("standalonepublisher") class ComponentsWidget(QtWidgets.QWidget): @@ -113,16 +123,103 @@ class ComponentsWidget(QtWidgets.QWidget): self.parent_widget.working_stop() def _publish(self): + log.info(self.parent_widget.pyblish_paths) self.working_start('Pyblish is running') try: data = self.parent_widget.collect_data() - publish.set_context( - data['project'], data['asset'], data['task'], 'standalonepublish' + set_context( + data['project'], + data['asset'], + data['task'] ) - result = publish.publish(data) + result = cli_publish(data, self.parent_widget.pyblish_paths) # Clear widgets from components list if publishing was successful if result: self.drop_frame.components_list.clear_widgets() self.drop_frame._refresh_view() finally: self.working_stop() + + +def set_context(project, asset, task): + ''' Sets context for pyblish (must be done before pyblish is launched) + :param project: Name of `Project` where instance should be published + :type project: str + :param asset: Name of `Asset` where instance should be published + :type asset: str + ''' + os.environ["AVALON_PROJECT"] = project + io.Session["AVALON_PROJECT"] = project + os.environ["AVALON_ASSET"] = asset + io.Session["AVALON_ASSET"] = asset + if not task: + task = '' + os.environ["AVALON_TASK"] = task + io.Session["AVALON_TASK"] = task + + io.install() + + av_project = io.find_one({'type': 'project'}) + av_asset = io.find_one({ + "type": 'asset', + "name": asset + }) + + parents = av_asset['data']['parents'] + hierarchy = '' + if parents and len(parents) > 0: + hierarchy = os.path.sep.join(parents) + + os.environ["AVALON_HIERARCHY"] = hierarchy + io.Session["AVALON_HIERARCHY"] = hierarchy + + os.environ["AVALON_PROJECTCODE"] = av_project['data'].get('code', '') + io.Session["AVALON_PROJECTCODE"] = av_project['data'].get('code', '') + + io.Session["current_dir"] = os.path.normpath(os.getcwd()) + + os.environ["AVALON_APP"] = "standalonepublish" + io.Session["AVALON_APP"] = "standalonepublish" + + io.uninstall() + + +def cli_publish(data, publish_paths, gui=True): + PUBLISH_SCRIPT_PATH = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "publish.py" + ) + io.install() + + # Create hash name folder in temp + chars = "".join([random.choice(string.ascii_letters) for i in range(15)]) + staging_dir = tempfile.mkdtemp(chars) + + # create also json and fill with data + json_data_path = staging_dir + os.path.basename(staging_dir) + '.json' + with open(json_data_path, 'w') as outfile: + json.dump(data, outfile) + + envcopy = os.environ.copy() + envcopy["PYBLISH_HOSTS"] = "standalonepublisher" + envcopy["SAPUBLISH_INPATH"] = json_data_path + envcopy["PYBLISHGUI"] = "pyblish_pype" + envcopy["PUBLISH_PATHS"] = os.pathsep.join(publish_paths) + if data.get("family", "").lower() == "editorial": + envcopy["PYBLISH_SUSPEND_LOGS"] = "1" + + result = execute( + [sys.executable, PUBLISH_SCRIPT_PATH], + env=envcopy + ) + + result = {} + if os.path.exists(json_data_path): + with open(json_data_path, "r") as f: + result = json.load(f) + + log.info(f"Publish result: {result}") + + io.uninstall() + + return False From 507be4926965271d5ab0db6f078ba1918ad7b656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Sat, 15 Aug 2020 00:09:04 +0200 Subject: [PATCH 386/947] tile rendering support in pype --- .../global/publish/submit_publish_job.py | 11 +- pype/plugins/maya/create/create_render.py | 2 + pype/plugins/maya/publish/collect_render.py | 2 + .../maya/publish/submit_maya_deadline.py | 284 +++++++++++++++++- 4 files changed, 280 insertions(+), 19 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 3053c80b11..d106175eb6 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -718,7 +718,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "pixelAspect": data.get("pixelAspect", 1), "resolutionWidth": data.get("resolutionWidth", 1920), "resolutionHeight": data.get("resolutionHeight", 1080), - "multipartExr": data.get("multipartExr", False) + "multipartExr": data.get("multipartExr", False), + "jobBatchName": data.get("jobBatchName", "") } if "prerender" in instance.data["families"]: @@ -895,8 +896,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # We still use data from it so lets fake it. # # Batch name reflect original scene name - render_job["Props"]["Batch"] = os.path.splitext(os.path.basename( - context.data.get("currentFile")))[0] + + if instance.data.get("assemblySubmissionJob"): + render_job["Props"]["Batch"] = instance.data.get("jobBatchName") + else: + render_job["Props"]["Batch"] = os.path.splitext( + os.path.basename(context.data.get("currentFile")))[0] # User is deadline user render_job["Props"]["User"] = context.data.get( "deadlineUser", getpass.getuser()) diff --git a/pype/plugins/maya/create/create_render.py b/pype/plugins/maya/create/create_render.py index 9e5f9310ae..5d68c5301a 100644 --- a/pype/plugins/maya/create/create_render.py +++ b/pype/plugins/maya/create/create_render.py @@ -185,6 +185,8 @@ class CreateRender(avalon.maya.Creator): self.data["useMayaBatch"] = False self.data["vrayScene"] = False self.data["tileRendering"] = False + self.data["tilesX"] = 2 + self.data["tilesY"] = 2 # Disable for now as this feature is not working yet # self.data["assScene"] = False diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 5ca9392080..1db7b31c8c 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -243,6 +243,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "resolutionHeight": cmds.getAttr("defaultResolution.height"), "pixelAspect": cmds.getAttr("defaultResolution.pixelAspect"), "tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501 + "tilesX": render_instance.data.get("tilesX") or 2, + "tilesY": render_instance.data.get("tilesY") or 2, "priority": render_instance.data.get("priority") } diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 7fe20c779d..d6d4bd2910 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -16,11 +16,14 @@ Attributes: """ +from __future__ import print_function import os import json import getpass import copy import re +import hashlib +from datetime import datetime import clique import requests @@ -61,6 +64,91 @@ payload_skeleton = { } +def _format_tiles(filename, index, tiles_x, tiles_y, width, height, prefix): + """Generate tile entries for Deadline tile job. + + Returns two dictionaries - one that can be directly used in Deadline + job, second that can be used for Deadline Assembly job configuration + file. + + This will format tile names: + + Example:: + { + "OutputFilename0Tile0": "_tile_1x1_4x4_Main_beauty.1001.exr", + "OutputFilename0Tile1": "_tile_2x1_4x4_Main_beauty.1001.exr" + } + + And add tile prefixes like: + + Example:: + Image prefix is: + `maya///_` + + Result for tile 0 for 4x4 will be: + `maya///_tile_1x1_4x4__` + + Calculating coordinates is tricky as in Job they are defined as top, + left, bottom, right with zero being in top-left corner. But Assembler + configuration file takes tile coordinates as X, Y, Width and Height and + zero is bottom left corner. + + Args: + filename (str): Filename to process as tiles. + index (int): Index of that file if it is sequence. + tiles_x (int): Number of tiles in X. + tiles_y (int): Number if tikes in Y. + width (int): Width resolution of final image. + height (int): Height resolution of final image. + prefix (str): Image prefix. + + Returns: + (dict, dict): Tuple of two dictionaires - first can be used to + extend JobInfo, second has tiles x, y, width and height + used for assembler configuration. + + """ + tile = 0 + out = {"JobInfo": {}, "PluginInfo": {}} + cfg = {} + w_space = width / tiles_x + h_space = height / tiles_y + + for tile_x in range(1, tiles_x + 1): + for tile_y in range(1, tiles_y + 1): + tile_prefix = "_tile_{}x{}_{}x{}_".format( + tile_x, tile_y, + tiles_x, + tiles_y + ) + out_tile_index = "OutputFilename{}Tile{}".format( + str(index), tile + ) + new_filename = "{}/{}{}".format( + os.path.dirname(filename), + tile_prefix, + os.path.basename(filename) + ) + out["JobInfo"][out_tile_index] = new_filename + out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = tile_prefix.join( # noqa: E501 + prefix.rsplit("/", 1)) + + out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 + out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 + out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 + out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + + cfg["Tile{}".format(tile)] = new_filename + cfg["Tile{}Tile".format(tile)] = new_filename + cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space + cfg["Tile{}Y".format(tile)] = (tile_y - 1) * h_space + cfg["Tile{}Width".format(tile)] = tile_x * w_space + cfg["Tile{}Height".format(tile)] = tile_y * h_space + + tile += 1 + return out, cfg + + def get_renderer_variables(renderlayer, root): """Retrieve the extension which has been set in the VRay settings. @@ -164,6 +252,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): optional = True use_published = True + tile_assembler_plugin = "DraftTileAssembler" def process(self, instance): """Plugin entry point.""" @@ -309,7 +398,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor payload_skeleton["JobInfo"]["OutputDirectory0"] = \ - os.path.dirname(output_filename_0) + os.path.dirname(output_filename_0).replace("\\", "/") payload_skeleton["JobInfo"]["OutputFilename0"] = \ output_filename_0.replace("\\", "/") @@ -376,9 +465,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Add list of expected files to job --------------------------------- exp = instance.data.get("expectedFiles") - - output_filenames = {} exp_index = 0 + output_filenames = {} if isinstance(exp[0], dict): # we have aovs and we need to iterate over them @@ -390,33 +478,202 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): assert len(rem) == 1, ("Found multiple non related files " "to render, don't know what to do " "with them.") - payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 output_file = rem[0] + if not instance.data.get("tileRendering"): + payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 else: output_file = col[0].format('{head}{padding}{tail}') - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 - output_filenames[exp_index] = output_file + if not instance.data.get("tileRendering"): + payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 + + output_filenames['OutputFilename' + str(exp_index)] = output_file # noqa: E501 exp_index += 1 else: - col, rem = clique.assemble(files) + col, rem = clique.assemble(exp) if not col and rem: # we couldn't find any collections but have # individual files. assert len(rem) == 1, ("Found multiple non related files " "to render, don't know what to do " "with them.") - payload['JobInfo']['OutputFilename' + str(exp_index)] = rem[0] # noqa: E501 + + output_file = rem[0] + if not instance.data.get("tileRendering"): + payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 else: output_file = col[0].format('{head}{padding}{tail}') - payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 + if not instance.data.get("tileRendering"): + payload['JobInfo']['OutputFilename' + str(exp_index)] = output_file # noqa: E501 + + output_filenames['OutputFilename' + str(exp_index)] = output_file plugin = payload["JobInfo"]["Plugin"] self.log.info("using render plugin : {}".format(plugin)) + # Store output dir for unified publisher (filesequence) + instance.data["outputDir"] = os.path.dirname(output_filename_0) + self.preflight_check(instance) - # Submit job to farm ------------------------------------------------ - if not instance.data.get("tileRendering"): + # Prepare tiles data ------------------------------------------------ + if instance.data.get("tileRendering"): + # if we have sequence of files, we need to create tile job for + # every frame + + payload["JobInfo"]["TileJob"] = True + payload["JobInfo"]["TileJobTilesInX"] = instance.data.get("tilesX") + payload["JobInfo"]["TileJobTilesInY"] = instance.data.get("tilesY") + payload["PluginInfo"]["ImageHeight"] = instance.data.get("resolutionHeight") # noqa: E501 + payload["PluginInfo"]["ImageWidth"] = instance.data.get("resolutionWidth") # noqa: E501 + payload["PluginInfo"]["RegionRendering"] = True + + assembly_payload = { + "AuxFiles": [], + "JobInfo": { + "BatchName": payload["JobInfo"]["BatchName"], + "Frames": 0, + "Name": "{} - Tile Assembly Job".format( + payload["JobInfo"]["Name"]), + "OutputDirectory0": + payload["JobInfo"]["OutputDirectory0"].replace( + "\\", "/"), + "Plugin": self.tile_assembler_plugin, + "MachineLimit": 1 + }, + "PluginInfo": { + "CleanupTiles": 1, + "ErrorOnMissing": True + } + } + assembly_payload["JobInfo"].update(output_filenames) + + frame_payloads = [] + assembly_payloads = {} + + R_FRAME_NUMBER = re.compile(r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 + REPL_FRAME_NUMBER = re.compile(r"(.+\.)([0-9]+)(\..+)") # noqa: N806, E501 + + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + # get files from `beauty` + files = exp[0].get("beauty") + if not files: + # if beauty doesn't exists, use first aov we found + files = exp[0].get(list(exp[0].keys())[0]) + else: + files = exp + + file_index = 1 + for file in files: + frame = re.search(R_FRAME_NUMBER, file).group("frame") + new_payload = copy.copy(payload) + new_payload["JobInfo"]["Name"] = \ + "{} (Frame {} - {} tiles)".format( + new_payload["JobInfo"]["Name"], + frame, + instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 + ) + new_payload["JobInfo"]["TileJobFrame"] = frame + + tiles_data = _format_tiles( + file, 0, + instance.data.get("tilesX"), + instance.data.get("tilesY"), + instance.data.get("resolutionWidth"), + instance.data.get("resolutionHeight"), + payload["PluginInfo"]["OutputFilePrefix"] + )[0] + new_payload["JobInfo"].update(tiles_data["JobInfo"]) + new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) + + job_hash = hashlib.sha256("{}_{}".format(file_index, file)) + new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() + new_payload["JobInfo"]["ExtraInfo1"] = file + + frame_payloads.append(new_payload) + + new_assembly_payload = copy.copy(assembly_payload) + new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( + REPL_FRAME_NUMBER, + "\\1{}\\3".format("#" * len(frame)), file) + + new_assembly_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() # noqa: E501 + new_assembly_payload["JobInfo"]["ExtraInfo1"] = file + assembly_payloads[job_hash.hexdigest()] = new_assembly_payload + file_index += 1 + + self.log.info( + "Submitting tile job(s) [{}] ...".format(len(frame_payloads))) + + url = "{}/api/jobs".format(self._deadline_url) + tiles_count = instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 + + for tile_job in frame_payloads: + response = self._requests_post(url, json=tile_job) + if not response.ok: + raise Exception(response.text) + + job_id = response.json()["_id"] + hash = response.json()["Props"]["Ex0"] + file = response.json()["Props"]["Ex1"] + assembly_payloads[hash]["JobInfo"]["JobDependency0"] = job_id + + # write assembly job config files + now = datetime.now() + + config_file = os.path.join( + os.path.dirname(output_filename_0), + "{}_config_{}.txt".format( + os.path.splitext(file)[0], + now.strftime("%Y_%m_%d_%H_%M_%S") + ) + ) + + try: + if not os.path.isdir(os.path.dirname(config_file)): + os.makedirs(os.path.dirname(config_file)) + except OSError: + # directory is not available + self.log.warning( + "Path is unreachable: `{}`".format( + os.path.dirname(config_file))) + + with open(config_file, "w") as cf: + print("TileCount={}".format(tiles_count), file=cf) + print("ImageFileName={}".format(file), file=cf) + print("ImageWidth={}".format( + instance.data.get("resolutionWidth")), file=cf) + print("ImageHeight={}".format( + instance.data.get("resolutionHeight")), file=cf) + + tiles = _format_tiles( + file, 0, + instance.data.get("tilesX"), + instance.data.get("tilesY"), + instance.data.get("resolutionWidth"), + instance.data.get("resolutionHeight"), + payload["PluginInfo"]["OutputFilePrefix"] + )[1] + sorted(tiles) + for k, v in tiles.items(): + print("{}={}".format(k, v), file=cf) + + self.log.debug(json.dumps(assembly_payloads, + indent=4, sort_keys=True)) + self.log.info( + "Submitting assembly job(s) [{}] ...".format(len(assembly_payloads))) # noqa: E501 + url = "{}/api/jobs".format(self._deadline_url) + response = self._requests_post(url, json={ + "Jobs": list(assembly_payloads.values()), + "AuxFiles": [] + }) + if not response.ok: + raise Exception(response) + + instance.data["assemblySubmissionJob"] = assembly_payloads + instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] + else: + # Submit job to farm -------------------------------------------- self.log.info("Submitting ...") self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) @@ -426,11 +683,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if not response.ok: raise Exception(response.text) instance.data["deadlineSubmissionJob"] = response.json() - else: - self.log.info("Skipping submission, tile rendering enabled.") - - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname(output_filename_0) def _get_maya_payload(self, data): payload = copy.deepcopy(payload_skeleton) From 7ef0c58019326c7d9b0524cac82727751c746d5d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 00:09:14 +0200 Subject: [PATCH 387/947] removed files that should not be in this PR --- pype/tools/config_setting/widgets/base0.py | 404 ---- pype/tools/config_setting/widgets/inputs0.py | 2145 ------------------ pype/tools/config_setting/widgets/inputs1.py | 2131 ----------------- 3 files changed, 4680 deletions(-) delete mode 100644 pype/tools/config_setting/widgets/base0.py delete mode 100644 pype/tools/config_setting/widgets/inputs0.py delete mode 100644 pype/tools/config_setting/widgets/inputs1.py diff --git a/pype/tools/config_setting/widgets/base0.py b/pype/tools/config_setting/widgets/base0.py deleted file mode 100644 index 7f01f27ca8..0000000000 --- a/pype/tools/config_setting/widgets/base0.py +++ /dev/null @@ -1,404 +0,0 @@ -import os -import json -import copy -from Qt import QtWidgets, QtCore, QtGui -from . import config -from .widgets import UnsavedChangesDialog -from .lib import NOT_SET -from avalon import io -from queue import Queue - - -class TypeToKlass: - types = {} - - -class PypeConfigurationWidget: - default_state = "" - - def config_value(self): - raise NotImplementedError( - "Method `config_value` is not implemented for `{}`.".format( - self.__class__.__name__ - ) - ) - - def value_from_values(self, values, keys=None): - if not values: - return NOT_SET - - if keys is None: - keys = self.keys - - value = values - for key in keys: - if not isinstance(value, dict): - raise TypeError( - "Expected dictionary got {}.".format(str(type(value))) - ) - - if key not in value: - return NOT_SET - value = value[key] - return value - - def style_state(self, is_overriden, is_modified): - items = [] - if is_overriden: - items.append("overriden") - if is_modified: - items.append("modified") - return "-".join(items) or self.default_state - - def add_children_gui(self, child_configuration, values): - raise NotImplementedError(( - "Method `add_children_gui` is not implemented for `{}`." - ).format(self.__class__.__name__)) - - -class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): - is_overidable = False - is_overriden = False - is_group = False - any_parent_is_group = False - ignore_value_changes = False - - def __init__(self, parent=None): - super(StudioWidget, self).__init__(parent) - - self.input_fields = [] - - scroll_widget = QtWidgets.QScrollArea(self) - content_widget = QtWidgets.QWidget(scroll_widget) - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) - content_layout.setSpacing(0) - content_layout.setAlignment(QtCore.Qt.AlignTop) - content_widget.setLayout(content_layout) - - # scroll_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - # scroll_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - scroll_widget.setWidgetResizable(True) - scroll_widget.setWidget(content_widget) - - self.scroll_widget = scroll_widget - self.content_layout = content_layout - self.content_widget = content_widget - - footer_widget = QtWidgets.QWidget() - footer_layout = QtWidgets.QHBoxLayout(footer_widget) - - save_btn = QtWidgets.QPushButton("Save") - spacer_widget = QtWidgets.QWidget() - footer_layout.addWidget(spacer_widget, 1) - footer_layout.addWidget(save_btn, 0) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.setLayout(layout) - - layout.addWidget(scroll_widget, 1) - layout.addWidget(footer_widget, 0) - - save_btn.clicked.connect(self._save) - - self.reset() - - def reset(self): - if self.content_layout.count() != 0: - for widget in self.input_fields: - self.content_layout.removeWidget(widget) - widget.deleteLater() - self.input_fields.clear() - - values = {"studio": config.studio_presets()} - schema = config.gui_schema("studio_schema", "studio_gui_schema") - self.keys = schema.get("keys", []) - self.add_children_gui(schema, values) - self.schema = schema - - def _save(self): - all_values = {} - for item in self.input_fields: - all_values.update(item.config_value()) - - for key in reversed(self.keys): - _all_values = {key: all_values} - all_values = _all_values - - # Skip first key - all_values = all_values["studio"] - - # Load studio data with metadata - current_presets = config.studio_presets() - - keys_to_file = config.file_keys_from_schema(self.schema) - for key_sequence in keys_to_file: - # Skip first key - key_sequence = key_sequence[1:] - subpath = "/".join(key_sequence) + ".json" - origin_values = current_presets - for key in key_sequence: - if key not in origin_values: - origin_values = {} - break - origin_values = origin_values[key] - - new_values = all_values - for key in key_sequence: - new_values = new_values[key] - origin_values.update(new_values) - - output_path = os.path.join( - config.studio_presets_path, subpath - ) - dirpath = os.path.dirname(output_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - with open(output_path, "w") as file_stream: - json.dump(origin_values, file_stream, indent=4) - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - item = klass( - child_configuration, values, self.keys, self - ) - self.input_fields.append(item) - self.content_layout.addWidget(item) - - -class ProjectListView(QtWidgets.QListView): - left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: - index = self.indexAt(event.pos()) - self.left_mouse_released_at.emit(index) - super(ProjectListView, self).mouseReleaseEvent(event) - - -class ProjectListWidget(QtWidgets.QWidget): - default = "< Default >" - project_changed = QtCore.Signal() - - def __init__(self, parent): - self._parent = parent - - self.current_project = None - - super(ProjectListWidget, self).__init__(parent) - - label_widget = QtWidgets.QLabel("Projects") - project_list = ProjectListView(self) - project_list.setModel(QtGui.QStandardItemModel()) - - # Do not allow editing - project_list.setEditTriggers( - QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers - ) - # Do not automatically handle selection - project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - - layout = QtWidgets.QVBoxLayout(self) - layout.setSpacing(3) - layout.addWidget(label_widget, 0) - layout.addWidget(project_list, 1) - - project_list.left_mouse_released_at.connect(self.on_item_clicked) - - self.project_list = project_list - - self.refresh() - - def on_item_clicked(self, new_index): - new_project_name = new_index.data(QtCore.Qt.DisplayRole) - if new_project_name is None: - return - - if self.current_project == new_project_name: - return - - save_changes = False - change_project = False - if self.validate_context_change(): - change_project = True - - else: - dialog = UnsavedChangesDialog(self) - result = dialog.exec_() - if result == 1: - save_changes = True - change_project = True - - elif result == 2: - change_project = True - - if save_changes: - self._parent._save() - - if change_project: - self.select_project(new_project_name) - self.current_project = new_project_name - self.project_changed.emit() - else: - self.select_project(self.current_project) - - def validate_context_change(self): - # TODO add check if project can be changed (is modified) - for item in self._parent.input_fields: - is_modified = item.child_modified - if is_modified: - return False - return True - - def project_name(self): - if self.current_project == self.default: - return None - return self.current_project - - def select_project(self, project_name): - model = self.project_list.model() - found_items = model.findItems(project_name) - if not found_items: - found_items = model.findItems(self.default) - - index = model.indexFromItem(found_items[0]) - self.project_list.selectionModel().clear() - self.project_list.selectionModel().setCurrentIndex( - index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent - ) - - def refresh(self): - selected_project = None - for index in self.project_list.selectedIndexes(): - selected_project = index.data(QtCore.Qt.DisplayRole) - break - - model = self.project_list.model() - model.clear() - items = [self.default] - io.install() - for project_doc in tuple(io.projects()): - items.append(project_doc["name"]) - - for item in items: - model.appendRow(QtGui.QStandardItem(item)) - - self.select_project(selected_project) - - self.current_project = self.project_list.currentIndex().data( - QtCore.Qt.DisplayRole - ) - - -class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): - is_overriden = False - is_group = False - any_parent_is_group = False - - def __init__(self, parent=None): - super(ProjectWidget, self).__init__(parent) - - self.is_overidable = False - self.ignore_value_changes = False - - self.input_fields = [] - - scroll_widget = QtWidgets.QScrollArea(self) - content_widget = QtWidgets.QWidget(scroll_widget) - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) - content_layout.setSpacing(0) - content_layout.setAlignment(QtCore.Qt.AlignTop) - content_widget.setLayout(content_layout) - - scroll_widget.setWidgetResizable(True) - scroll_widget.setWidget(content_widget) - - project_list_widget = ProjectListWidget(self) - content_layout.addWidget(project_list_widget) - - footer_widget = QtWidgets.QWidget() - footer_layout = QtWidgets.QHBoxLayout(footer_widget) - - save_btn = QtWidgets.QPushButton("Save") - spacer_widget = QtWidgets.QWidget() - footer_layout.addWidget(spacer_widget, 1) - footer_layout.addWidget(save_btn, 0) - - presets_widget = QtWidgets.QWidget() - presets_layout = QtWidgets.QVBoxLayout(presets_widget) - presets_layout.setContentsMargins(0, 0, 0, 0) - presets_layout.setSpacing(0) - - presets_layout.addWidget(scroll_widget, 1) - presets_layout.addWidget(footer_widget, 0) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - self.setLayout(layout) - - layout.addWidget(project_list_widget, 0) - layout.addWidget(presets_widget, 1) - - save_btn.clicked.connect(self._save) - project_list_widget.project_changed.connect(self._on_project_change) - - self.project_list_widget = project_list_widget - self.scroll_widget = scroll_widget - self.content_layout = content_layout - self.content_widget = content_widget - - self.reset() - - def reset(self): - values = config.global_project_presets() - schema = config.gui_schema("projects_schema", "project_gui_schema") - self.keys = schema.get("keys", []) - self.add_children_gui(schema, values) - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - item = klass( - child_configuration, values, self.keys, self - ) - self.input_fields.append(item) - self.content_layout.addWidget(item) - - def _on_project_change(self): - project_name = self.project_list_widget.project_name() - - if project_name is None: - overrides = None - self.is_overidable = False - else: - overrides = config.project_preset_overrides(project_name) - self.is_overidable = True - - self.ignore_value_changes = True - for item in self.input_fields: - item.apply_overrides(overrides) - self.ignore_value_changes = False - - def _save(self): - output = {} - for item in self.input_fields: - if hasattr(item, "override_value"): - print(item.override_value()) - else: - print("*** missing `override_value`", item) - - # for item in self.input_fields: - # output.update(item.config_value()) - # - # for key in reversed(self.keys): - # _output = {key: output} - # output = _output - - print(json.dumps(output, indent=4)) diff --git a/pype/tools/config_setting/widgets/inputs0.py b/pype/tools/config_setting/widgets/inputs0.py deleted file mode 100644 index 7ab9c7fa01..0000000000 --- a/pype/tools/config_setting/widgets/inputs0.py +++ /dev/null @@ -1,2145 +0,0 @@ -import json -from Qt import QtWidgets, QtCore, QtGui -from . import config -from .base import PypeConfigurationWidget, TypeToKlass -from .widgets import ( - ClickableWidget, - ExpandingWidget, - ModifiedIntSpinBox, - ModifiedFloatSpinBox -) -from .lib import NOT_SET, AS_WIDGET - - -class SchemeGroupHierarchyBug(Exception): - def __init__(self, msg=None): - if not msg: - # TODO better message - msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" - super(SchemeGroupHierarchyBug, self).__init__(msg) - - -class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._as_widget = as_widget - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - self.override_value = None - - super(BooleanWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.checkbox = QtWidgets.QCheckBox() - self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - layout.addWidget(label_widget) - - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - layout.addWidget(self.checkbox) - - self.default_value = self.item_value() - - self.checkbox.stateChanged.connect(self._on_value_change) - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, bool): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - def set_value(self, value, *, default_value=False): - # Ignore value change because if `self.isChecked()` has same - # value as `value` the `_on_value_change` is not triggered - self.checkbox.setChecked(value) - - if default_value: - self.default_value = self.item_value() - - self._on_value_change() - - def reset_value(self): - if self.is_overidable and self.override_value is not None: - self.set_value(self.override_value) - else: - self.set_value(self.default_value) - - def clear_value(self): - self.reset_value() - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - _value = self.item_value() - is_modified = None - if self.is_overidable: - self._is_overriden = True - if self.override_value is not None: - is_modified = _value != self.override_value - - if is_modified is None: - is_modified = _value != self.default_value - - self._is_modified = is_modified - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - else: - property_name = "state" - - self.label_widget.setProperty(property_name, state) - self.label_widget.style().polish(self.label_widget) - self._state = state - - def item_value(self): - return self.checkbox.isChecked() - - def config_value(self): - return {self.key: self.item_value()} - - -class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(IntegerWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.int_input = ModifiedIntSpinBox() - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.int_input) - - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.default_value = self.item_value() - self.override_value = None - - self.int_input.valueChanged.connect(self._on_value_change) - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, int): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.int_input.setValue(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def clear_value(self): - self.set_value(0) - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.int_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.int_input.value() - - def config_value(self): - return {self.key: self.item_value()} - - -class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(FloatWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.float_input = ModifiedFloatSpinBox() - - decimals = input_data.get("decimals", 5) - maximum = input_data.get("maximum") - minimum = input_data.get("minimum") - - self.float_input.setDecimals(decimals) - if maximum is not None: - self.float_input.setMaximum(float(maximum)) - if minimum is not None: - self.float_input.setMinimum(float(minimum)) - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.float_input) - - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.default_value = self.item_value() - self.override_value = None - - self.float_input.valueChanged.connect(self._on_value_change) - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, float): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.float_input.setValue(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def clear_value(self): - self.set_value(0) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.float_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.float_input.value() - - def config_value(self): - return {self.key: self.item_value()} - - -class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(TextSingleLineWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.text_input = QtWidgets.QLineEdit() - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) - - if not self._as_widget: - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.default_value = self.item_value() - self.override_value = None - - self.text_input.textChanged.connect(self._on_value_change) - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, str): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.text_input.setText(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def clear_value(self): - self.set_value("") - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.text() - - def config_value(self): - return {self.key: self.item_value()} - - -class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(TextMultiLineWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.text_input = QtWidgets.QPlainTextEdit() - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) - - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.default_value = self.item_value() - self.override_value = None - - self.text_input.textChanged.connect(self._on_value_change) - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, str): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.text_input.setPlainText(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def clear_value(self): - self.set_value("") - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.toPlainText() - - def config_value(self): - return {self.key: self.item_value()} - - -class RawJsonInput(QtWidgets.QPlainTextEdit): - tab_length = 4 - - def __init__(self, *args, **kwargs): - super(RawJsonInput, self).__init__(*args, **kwargs) - self.setObjectName("RawJsonInput") - self.setTabStopDistance( - QtGui.QFontMetricsF( - self.font() - ).horizontalAdvance(" ") * self.tab_length - ) - - self.is_valid = None - - def set_value(self, value, *, default_value=False): - self.setPlainText(value) - - def setPlainText(self, *args, **kwargs): - super(RawJsonInput, self).setPlainText(*args, **kwargs) - self.validate() - - def focusOutEvent(self, event): - super(RawJsonInput, self).focusOutEvent(event) - self.validate() - - def validate_value(self, value): - if isinstance(value, str) and not value: - return True - - try: - json.loads(value) - return True - except Exception: - return False - - def update_style(self, is_valid=None): - if is_valid is None: - return self.validate() - - if is_valid != self.is_valid: - self.is_valid = is_valid - if is_valid: - state = "" - else: - state = "invalid" - self.setProperty("state", state) - self.style().polish(self) - - def value(self): - return self.toPlainText() - - def validate(self): - value = self.value() - is_valid = self.validate_value(value) - self.update_style(is_valid) - - -class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(RawJsonWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.text_input = RawJsonInput() - - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) - - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.default_value = self.item_value() - self.override_value = None - - self.text_input.textChanged.connect(self._on_value_change) - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, str): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.text_input.setPlainText(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def clear_value(self): - self.set_value("") - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.toPlainText() - - def config_value(self): - return {self.key: self.item_value()} - - -class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): - _btn_size = 20 - value_changed = QtCore.Signal(object) - - def __init__(self, parent): - super(TextListItem, self).__init__(parent) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(3) - - self.text_input = QtWidgets.QLineEdit() - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") - - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") - - layout.addWidget(self.text_input, 1) - layout.addWidget(self.add_btn, 0) - layout.addWidget(self.remove_btn, 0) - - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) - - self.text_input.textChanged.connect(self._on_value_change) - - self.is_single = False - - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def row(self): - return self.parent().input_fields.index(self) - - def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) - - def on_remove_clicked(self): - if self.is_single: - self.text_input.setText("") - else: - self.parent().remove_row(self) - - def config_value(self): - return self.text_input.text() - - -class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__(self, input_data, as_widget, parent_keys, parent): - super(TextListSubWidget, self).__init__(parent) - self.setObjectName("TextListSubWidget") - - self.as_widget = as_widget - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(5) - self.setLayout(layout) - - self.input_fields = [] - self.add_row() - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.default_value = self.item_value() - self.override_value = None - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, (list, tuple)): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - def set_value(self, value, *, default_value=False): - for input_field in self.input_fields: - self.remove_row(input_field) - - for item_text in value: - self.add_row(text=item_text) - - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def clear_value(self): - self.set_value([]) - - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def count(self): - return len(self.input_fields) - - def add_row(self, row=None, text=None): - # Create new item - item_widget = TextListItem(self) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False - - item_widget.value_changed.connect(self._on_value_change) - - if row is None: - self.layout().addWidget(item_widget) - self.input_fields.append(item_widget) - else: - self.layout().insertWidget(row, item_widget) - self.input_fields.insert(row, item_widget) - - # Set text if entered text is not None - # else (when add button clicked) trigger `_on_value_change` - if text is not None: - item_widget.text_input.setText(text) - else: - self._on_value_change() - self.parent().updateGeometry() - - def remove_row(self, item_widget): - item_widget.value_changed.disconnect() - - self.layout().removeWidget(item_widget) - self.input_fields.remove(item_widget) - item_widget.setParent(None) - item_widget.deleteLater() - - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True - - self._on_value_change() - self.parent().updateGeometry() - - def item_value(self): - output = [] - for item in self.input_fields: - text = item.config_value() - if text: - output.append(text) - - return output - - def config_value(self): - return {self.key: self.item_value()} - - -class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self._is_modified = False - self.is_group = is_group - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(TextListWidget, self).__init__(parent) - self.setObjectName("TextListWidget") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - - self.label_widget = label_widget - # keys = list(parent_keys) - # keys.append(input_data["key"]) - # self.keys = keys - - self.value_widget = TextListSubWidget( - input_data, values, parent_keys, self - ) - self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - self.value_widget.value_changed.connect(self._on_value_change) - - # self.value_widget.se - self.key = input_data["key"] - layout.addWidget(self.value_widget) - self.setLayout(layout) - - self.default_value = self.item_value() - self.override_value = None - - def set_default_values(self, default_values): - value = self.value_from_values(default_values) - if isinstance(value, (list, tuple)): - self.set_value(value, default_value=True) - self.default_value = self.item_value() - else: - self.default_value = value - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def set_value(self, value, *, default_value=False): - self.value_widget.set_value(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def clear_value(self): - self.set_value([]) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - def item_value(self): - return self.value_widget.config_value() - - def config_value(self): - return {self.key: self.item_value()} - - -class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - if as_widget: - raise TypeError("Can't use \"{}\" as widget item.".format( - self.__class__.__name__ - )) - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - self.any_parent_is_group = any_parent_is_group - - self._is_modified = False - self._is_overriden = False - self.is_group = is_group - - self._state = None - self._child_state = None - - super(DictExpandWidget, self).__init__(parent) - self.setObjectName("DictExpandWidget") - top_part = ClickableWidget(parent=self) - - button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) - button_toggle.setProperty("btn-type", "expand-toggle") - button_toggle.setIconSize(button_size) - button_toggle.setArrowType(QtCore.Qt.RightArrow) - button_toggle.setCheckable(True) - button_toggle.setChecked(False) - - label = input_data["label"] - button_toggle_text = QtWidgets.QLabel(label, parent=top_part) - button_toggle_text.setObjectName("ExpandLabel") - - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(button_toggle_text) - top_part.setLayout(layout) - - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 9) - - content_widget = QtWidgets.QWidget(self) - content_widget.setVisible(False) - - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) - - main_layout.addWidget(top_part) - main_layout.addWidget(content_widget) - self.setLayout(main_layout) - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - self.top_part = top_part - self.button_toggle = button_toggle - self.button_toggle_text = button_toggle_text - - self.content_widget = content_widget - self.content_layout = content_layout - - self.top_part.clicked.connect(self._top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) - - self.input_fields = [] - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def set_default_values(self, default_values): - for input_field in self.input_fields: - input_field.set_default_values(default_values) - - def _top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) - - def toggle_content(self, *args): - if len(args) > 0: - checked = args[0] - else: - checked = self.button_toggle.isChecked() - arrow_type = QtCore.Qt.RightArrow - if checked: - arrow_type = QtCore.Qt.DownArrow - self.button_toggle.setChecked(checked) - self.button_toggle.setArrowType(arrow_type) - self.content_widget.setVisible(checked) - self.parent().updateGeometry() - - def resizeEvent(self, event): - super(DictExpandWidget, self).resizeEvent(event) - self.content_widget.updateGeometry() - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def apply_overrides(self, override_value): - # Make sure this is set to False - self._is_overriden = False - self._state = None - self._child_state = None - for item in self.input_fields: - if override_value is None: - child_value = None - else: - child_value = override_value.get(item.key) - - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden - ) - ) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - - # TODO update items - if item is not None: - for _item in self.input_fields: - if _item is not item: - _item.update_style() - - self.value_changed.emit(self) - - self.update_style() - - def update_style(self, is_overriden=None): - child_modified = self.child_modified - child_state = self.style_state(self.child_overriden, child_modified) - if child_state: - child_state = "child-{}".format(child_state) - - if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) - self._child_state = child_state - - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - self.button_toggle_text.setProperty("state", state) - self.button_toggle_text.style().polish(self.button_toggle_text) - - self._state = state - - @property - def is_modified(self): - if self.is_group: - return self.child_modified - return False - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return {self.key: self.item_value()} - - @property - def is_overidable(self): - return self._parent.is_overidable - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - item = klass( - child_configuration, values, self.keys, self - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addWidget(item) - - self.input_fields.append(item) - return item - - -class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): - # TODO is not overridable by itself - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - self.any_parent_is_group = any_parent_is_group - - self._is_overriden = False - self.is_modified = False - self.is_group = is_group - - super(DictInvisible, self).__init__(parent) - self.setObjectName("DictInvisible") - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - self.input_fields = [] - - self.key = input_data["key"] - self.keys = list(parent_keys) - self.keys.append(self.key) - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def set_default_values(self, default_values): - for input_field in self.input_fields: - input_field.set_default_values(default_values) - - def update_style(self, *args, **kwargs): - return - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return {self.key: self.item_value()} - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - if item_type == "schema": - for _schema in child_configuration["children"]: - children = config.gui_schema(_schema) - self.add_children_gui(children, values) - return - - klass = TypeToKlass.types.get(item_type) - item = klass( - child_configuration, values, self.keys, self - ) - self.layout().addWidget(item) - - item.value_changed.connect(self._on_value_change) - - self.input_fields.append(item) - return item - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - # TODO update items - if item is not None: - is_overriden = self.is_overriden - for _item in self.input_fields: - if _item is not item: - _item.update_style(is_overriden) - - self.value_changed.emit(self) - - def apply_overrides(self, override_value): - self._is_overriden = False - for item in self.input_fields: - if override_value is None: - child_value = None - else: - child_value = override_value.get(item.key) - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden - ) - ) - self.update_style() - - -class DictFormWidget(QtWidgets.QWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self.any_parent_is_group = any_parent_is_group - - self.is_modified = False - self.is_overriden = False - self.is_group = False - - super(DictFormWidget, self).__init__(parent) - - self.input_fields = {} - self.content_layout = QtWidgets.QFormLayout(self) - - self.keys = list(parent_keys) - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def set_default_values(self, default_values): - for key, input_field in self.input_fields.items(): - input_field.set_default_values(default_values) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - self.value_changed.emit(self) - - def item_value(self): - output = {} - for input_field in self.input_fields.values(): - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - @property - def child_modified(self): - for input_field in self.input_fields.values(): - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields.values(): - if input_field.child_overriden: - return True - return False - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def config_value(self): - return self.item_value() - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - key = child_configuration["key"] - # Pop label to not be set in child - label = child_configuration["label"] - - klass = TypeToKlass.types.get(item_type) - - label_widget = QtWidgets.QLabel(label) - - item = klass( - child_configuration, values, self.keys, self, label_widget - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addRow(label_widget, item) - self.input_fields[key] = item - return item - - -class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): - _btn_size = 20 - value_changed = QtCore.Signal(object) - - def __init__(self, object_type, parent): - self._parent = parent - - super(ModifiableDictItem, self).__init__(parent) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(3) - - ItemKlass = TypeToKlass.types[object_type] - - self.key_input = QtWidgets.QLineEdit() - self.key_input.setObjectName("DictKey") - - self.value_input = ItemKlass( - {}, - AS_WIDGET, - [], - self, - None - ) - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") - - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") - - layout.addWidget(self.key_input, 0) - layout.addWidget(self.value_input, 1) - layout.addWidget(self.add_btn, 0) - layout.addWidget(self.remove_btn, 0) - - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) - - self.key_input.textChanged.connect(self._on_value_change) - self.value_input.value_changed.connect(self._on_value_change) - - self.default_key = self._key() - self.default_value = self.value_input.item_value() - - self.override_key = None - self.override_value = None - - self.is_single = False - - def _key(self): - return self.key_input.text() - - def _on_value_change(self, item=None): - self.update_style() - self.value_changed.emit(self) - - @property - def is_group(self): - return self._parent.is_group - - @property - def any_parent_is_group(self): - return self._parent.any_parent_is_group - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def is_key_modified(self): - return self._key() != self.default_key - - def is_value_modified(self): - return self.value_input.is_modified - - @property - def is_modified(self): - return self.is_value_modified() or self.is_key_modified() - - def update_style(self): - if self.is_key_modified(): - state = "modified" - else: - state = "" - - self.key_input.setProperty("state", state) - self.key_input.style().polish(self.key_input) - - def row(self): - return self.parent().input_fields.index(self) - - def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) - - def on_remove_clicked(self): - if self.is_single: - self.value_input.clear_value() - self.key_input.setText("") - else: - self.parent().remove_row(self) - - def config_value(self): - key = self.key_input.text() - value = self.value_input.item_value() - if not key: - return {} - return {key: value} - - -class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__(self, input_data, as_widget, parent_keys, parent): - self._parent = parent - - super(ModifiableDictSubWidget, self).__init__(parent) - self.setObjectName("ModifiableDictSubWidget") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(5) - self.setLayout(layout) - - self.input_fields = [] - self.object_type = input_data["object_type"] - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - if self.count() == 0: - self.add_row() - - self.default_value = self.config_value() - self.override_value = None - - def set_default_values(self, default_values): - for input_field in self.input_fields: - self.remove_row(input_field) - - value = self.value_from_values(default_values) - if value is NOT_SET: - self.defaul_value = value - return - - for item_key, item_value in value.items(): - self.add_row(key=item_key, value=item_value) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._parent.is_overriden - - @property - def is_group(self): - return self._parent.is_group - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - @property - def any_parent_is_group(self): - return self._parent.any_parent_is_group - - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def count(self): - return len(self.input_fields) - - def add_row(self, row=None, key=None, value=None): - # Create new item - item_widget = ModifiableDictItem(self.object_type, self) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False - - item_widget.value_changed.connect(self._on_value_change) - - if row is None: - self.layout().addWidget(item_widget) - self.input_fields.append(item_widget) - else: - self.layout().insertWidget(row, item_widget) - self.input_fields.insert(row, item_widget) - - # Set value if entered value is not None - # else (when add button clicked) trigger `_on_value_change` - if value is not None and key is not None: - item_widget.default_key = key - item_widget.key_input.setText(key) - item_widget.value_input.set_value(value, default_value=True) - else: - self._on_value_change() - self.parent().updateGeometry() - - def remove_row(self, item_widget): - item_widget.value_changed.disconnect() - - self.layout().removeWidget(item_widget) - self.input_fields.remove(item_widget) - item_widget.setParent(None) - item_widget.deleteLater() - - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True - - self._on_value_change() - self.parent().updateGeometry() - - def config_value(self): - output = {} - for item in self.input_fields: - item_value = item.config_value() - if item_value: - output.update(item_value) - return output - - -class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): - # Should be used only for dictionary with one datatype as value - # TODO this is actually input field (do not care if is group or not) - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent_keys, parent, - as_widget=False, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.any_parent_is_group = any_parent_is_group - - self.is_group = is_group - self._is_modified = False - self._is_overriden = False - self._was_overriden = False - self._state = None - - super(ModifiableDict, self).__init__(input_data["label"], parent) - self.setObjectName("ModifiableDict") - - self.value_widget = ModifiableDictSubWidget( - input_data, as_widget, parent_keys, self - ) - self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - self.value_widget.value_changed.connect(self._on_value_change) - - self.set_content_widget(self.value_widget) - - self.key = input_data["key"] - - self.default_value = self.item_value() - self.override_value = None - - def set_default_values(self, default_values): - self.value_widget.set_default_values(default_values) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self.is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.default_value - - self.value_changed.emit(self) - - self.update_style() - - @property - def child_modified(self): - return self.is_modified - - @property - def is_modified(self): - return self._is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def is_modified(self): - return self._is_modified - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def apply_overrides(self, override_value): - self._state = None - self._is_modified = False - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if state: - child_state = "child-{}".format(state) - else: - child_state = "" - - self.setProperty("state", child_state) - self.style().polish(self) - - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - self._state = state - - def item_value(self): - return self.value_widget.config_value() - - def config_value(self): - return {self.key: self.item_value()} - - -TypeToKlass.types["boolean"] = BooleanWidget -TypeToKlass.types["text-singleline"] = TextSingleLineWidget -TypeToKlass.types["text-multiline"] = TextMultiLineWidget -TypeToKlass.types["raw-json"] = RawJsonWidget -TypeToKlass.types["int"] = IntegerWidget -TypeToKlass.types["float"] = FloatWidget -TypeToKlass.types["dict-expanding"] = DictExpandWidget -TypeToKlass.types["dict-form"] = DictFormWidget -TypeToKlass.types["dict-invisible"] = DictInvisible -TypeToKlass.types["dict-modifiable"] = ModifiableDict -TypeToKlass.types["list-text"] = TextListWidget diff --git a/pype/tools/config_setting/widgets/inputs1.py b/pype/tools/config_setting/widgets/inputs1.py deleted file mode 100644 index f9eb60f31a..0000000000 --- a/pype/tools/config_setting/widgets/inputs1.py +++ /dev/null @@ -1,2131 +0,0 @@ -import json -from Qt import QtWidgets, QtCore, QtGui -from . import config -from .base import PypeConfigurationWidget, TypeToKlass -from .widgets import ( - ClickableWidget, - ExpandingWidget, - ModifiedIntSpinBox, - ModifiedFloatSpinBox -) -from .lib import NOT_SET, AS_WIDGET - - -class SchemeGroupHierarchyBug(Exception): - def __init__(self, msg=None): - if not msg: - # TODO better message - msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" - super(SchemeGroupHierarchyBug, self).__init__(msg) - - -class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._as_widget = values is AS_WIDGET - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(BooleanWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.checkbox = QtWidgets.QCheckBox() - self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - layout.addWidget(label_widget) - - layout.addWidget(self.checkbox) - - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.checkbox.setChecked(value) - - self.default_value = self.item_value() - self.override_value = None - - self.checkbox.stateChanged.connect(self._on_value_change) - - def set_value(self, value, *, default_value=False): - # Ignore value change because if `self.isChecked()` has same - # value as `value` the `_on_value_change` is not triggered - self.checkbox.setChecked(value) - - if default_value: - self.default_value = self.item_value() - - self._on_value_change() - - def reset_value(self): - if self.is_overidable and self.override_value is not None: - self.set_value(self.override_value) - else: - self.set_value(self.default_value) - - def clear_value(self): - self.reset_value() - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - _value = self.item_value() - is_modified = None - if self.is_overidable: - self._is_overriden = True - if self.override_value is not None: - is_modified = _value != self.override_value - - if is_modified is None: - is_modified = _value != self.default_value - - self._is_modified = is_modified - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - else: - property_name = "state" - - self.label_widget.setProperty(property_name, state) - self.label_widget.style().polish(self.label_widget) - self._state = state - - def item_value(self): - return self.checkbox.isChecked() - - def config_value(self): - return {self.key: self.item_value()} - - def override_value(self): - if self.is_overriden: - output = { - "is_group": self.is_group, - "value": self.config_value() - } - return output - - -class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - self._as_widget = values is AS_WIDGET - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(IntegerWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.int_input = ModifiedIntSpinBox() - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.int_input) - - if not self._as_widget: - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.int_input.setValue(value) - - self.default_value = self.item_value() - self.override_value = None - - self.int_input.valueChanged.connect(self._on_value_change) - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.int_input.setValue(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def clear_value(self): - self.set_value(0) - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.int_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.int_input.value() - - def config_value(self): - return {self.key: self.item_value()} - - -class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - self._as_widget = values is AS_WIDGET - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(FloatWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.float_input = ModifiedFloatSpinBox() - - decimals = input_data.get("decimals", 5) - maximum = input_data.get("maximum") - minimum = input_data.get("minimum") - - self.float_input.setDecimals(decimals) - if maximum is not None: - self.float_input.setMaximum(float(maximum)) - if minimum is not None: - self.float_input.setMinimum(float(minimum)) - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.float_input) - - if not self._as_widget: - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.float_input.setValue(value) - - self.default_value = self.item_value() - self.override_value = None - - self.float_input.valueChanged.connect(self._on_value_change) - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.float_input.setValue(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def clear_value(self): - self.set_value(0) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.float_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.float_input.value() - - def config_value(self): - return {self.key: self.item_value()} - - -class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - self._as_widget = values is AS_WIDGET - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(TextSingleLineWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.text_input = QtWidgets.QLineEdit() - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) - - if not self._as_widget: - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.text_input.setText(value) - - self.default_value = self.item_value() - self.override_value = None - - self.text_input.textChanged.connect(self._on_value_change) - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.text_input.setText(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def clear_value(self): - self.set_value("") - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.text() - - def config_value(self): - return {self.key: self.item_value()} - - -class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - self._as_widget = values is AS_WIDGET - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(TextMultiLineWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.text_input = QtWidgets.QPlainTextEdit() - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) - - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.text_input.setPlainText(value) - - self.default_value = self.item_value() - self.override_value = None - - self.text_input.textChanged.connect(self._on_value_change) - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.text_input.setPlainText(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def clear_value(self): - self.set_value("") - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.toPlainText() - - def config_value(self): - return {self.key: self.item_value()} - - -class RawJsonInput(QtWidgets.QPlainTextEdit): - tab_length = 4 - - def __init__(self, *args, **kwargs): - super(RawJsonInput, self).__init__(*args, **kwargs) - self.setObjectName("RawJsonInput") - self.setTabStopDistance( - QtGui.QFontMetricsF( - self.font() - ).horizontalAdvance(" ") * self.tab_length - ) - - self.is_valid = None - - def set_value(self, value, *, default_value=False): - self.setPlainText(value) - - def setPlainText(self, *args, **kwargs): - super(RawJsonInput, self).setPlainText(*args, **kwargs) - self.validate() - - def focusOutEvent(self, event): - super(RawJsonInput, self).focusOutEvent(event) - self.validate() - - def validate_value(self, value): - if isinstance(value, str) and not value: - return True - - try: - json.loads(value) - return True - except Exception: - return False - - def update_style(self, is_valid=None): - if is_valid is None: - return self.validate() - - if is_valid != self.is_valid: - self.is_valid = is_valid - if is_valid: - state = "" - else: - state = "invalid" - self.setProperty("state", state) - self.style().polish(self) - - def value(self): - return self.toPlainText() - - def validate(self): - value = self.value() - is_valid = self.validate_value(value) - self.update_style(is_valid) - - -class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - self._as_widget = values is AS_WIDGET - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group - self._is_modified = False - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(RawJsonWidget, self).__init__(parent) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - self.text_input = RawJsonInput() - - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) - - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.text_input.setPlainText(value) - - self.default_value = self.item_value() - self.override_value = None - - self.text_input.textChanged.connect(self._on_value_change) - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def set_value(self, value, *, default_value=False): - self.text_input.setPlainText(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def clear_value(self): - self.set_value("") - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.toPlainText() - - def config_value(self): - return {self.key: self.item_value()} - - -class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): - _btn_size = 20 - value_changed = QtCore.Signal(object) - - def __init__(self, parent): - super(TextListItem, self).__init__(parent) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(3) - - self.text_input = QtWidgets.QLineEdit() - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") - - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") - - layout.addWidget(self.text_input, 1) - layout.addWidget(self.add_btn, 0) - layout.addWidget(self.remove_btn, 0) - - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) - - self.text_input.textChanged.connect(self._on_value_change) - - self.is_single = False - - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def row(self): - return self.parent().input_fields.index(self) - - def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) - - def on_remove_clicked(self): - if self.is_single: - self.text_input.setText("") - else: - self.parent().remove_row(self) - - def config_value(self): - return self.text_input.text() - - -class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__(self, input_data, values, parent_keys, parent): - super(TextListSubWidget, self).__init__(parent) - self.setObjectName("TextListSubWidget") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(5) - self.setLayout(layout) - - self.input_fields = [] - self.add_row() - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - self.set_value(value) - - self.default_value = self.item_value() - self.override_value = None - - def set_value(self, value, *, default_value=False): - for input_field in self.input_fields: - self.remove_row(input_field) - - for item_text in value: - self.add_row(text=item_text) - - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def clear_value(self): - self.set_value([]) - - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def count(self): - return len(self.input_fields) - - def add_row(self, row=None, text=None): - # Create new item - item_widget = TextListItem(self) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False - - item_widget.value_changed.connect(self._on_value_change) - - if row is None: - self.layout().addWidget(item_widget) - self.input_fields.append(item_widget) - else: - self.layout().insertWidget(row, item_widget) - self.input_fields.insert(row, item_widget) - - # Set text if entered text is not None - # else (when add button clicked) trigger `_on_value_change` - if text is not None: - item_widget.text_input.setText(text) - else: - self._on_value_change() - self.parent().updateGeometry() - - def remove_row(self, item_widget): - item_widget.value_changed.disconnect() - - self.layout().removeWidget(item_widget) - self.input_fields.remove(item_widget) - item_widget.setParent(None) - item_widget.deleteLater() - - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True - - self._on_value_change() - self.parent().updateGeometry() - - def item_value(self): - output = [] - for item in self.input_fields: - text = item.config_value() - if text: - output.append(text) - - return output - - def config_value(self): - return {self.key: self.item_value()} - - -class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self._is_modified = False - self.is_group = is_group - self._was_overriden = False - self._is_overriden = False - - self._state = None - - super(TextListWidget, self).__init__(parent) - self.setObjectName("TextListWidget") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - - self.label_widget = label_widget - # keys = list(parent_keys) - # keys.append(input_data["key"]) - # self.keys = keys - - self.value_widget = TextListSubWidget( - input_data, values, parent_keys, self - ) - self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - self.value_widget.value_changed.connect(self._on_value_change) - - # self.value_widget.se - self.key = input_data["key"] - layout.addWidget(self.value_widget) - self.setLayout(layout) - - self.default_value = self.item_value() - self.override_value = None - - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - self._is_modified = self.item_value() != self.default_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def set_value(self, value, *, default_value=False): - self.value_widget.set_value(value) - if default_value: - self.default_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.default_value) - - def clear_value(self): - self.set_value([]) - - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is None: - self._is_overriden = False - value = self.default_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - def item_value(self): - return self.value_widget.config_value() - - def config_value(self): - return {self.key: self.item_value()} - - -class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - if values is AS_WIDGET: - raise TypeError("Can't use \"{}\" as widget item.".format( - self.__class__.__name__ - )) - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - self.any_parent_is_group = any_parent_is_group - - self._is_modified = False - self._is_overriden = False - self.is_group = is_group - - self._state = None - self._child_state = None - - super(DictExpandWidget, self).__init__(parent) - self.setObjectName("DictExpandWidget") - top_part = ClickableWidget(parent=self) - - button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) - button_toggle.setProperty("btn-type", "expand-toggle") - button_toggle.setIconSize(button_size) - button_toggle.setArrowType(QtCore.Qt.RightArrow) - button_toggle.setCheckable(True) - button_toggle.setChecked(False) - - label = input_data["label"] - button_toggle_text = QtWidgets.QLabel(label, parent=top_part) - button_toggle_text.setObjectName("ExpandLabel") - - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(button_toggle_text) - top_part.setLayout(layout) - - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 9) - - content_widget = QtWidgets.QWidget(self) - content_widget.setVisible(False) - - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) - - main_layout.addWidget(top_part) - main_layout.addWidget(content_widget) - self.setLayout(main_layout) - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - self.top_part = top_part - self.button_toggle = button_toggle - self.button_toggle_text = button_toggle_text - - self.content_widget = content_widget - self.content_layout = content_layout - - self.top_part.clicked.connect(self._top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) - - self.input_fields = [] - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def _top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) - - def toggle_content(self, *args): - if len(args) > 0: - checked = args[0] - else: - checked = self.button_toggle.isChecked() - arrow_type = QtCore.Qt.RightArrow - if checked: - arrow_type = QtCore.Qt.DownArrow - self.button_toggle.setChecked(checked) - self.button_toggle.setArrowType(arrow_type) - self.content_widget.setVisible(checked) - self.parent().updateGeometry() - - def resizeEvent(self, event): - super(DictExpandWidget, self).resizeEvent(event) - self.content_widget.updateGeometry() - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def apply_overrides(self, override_value): - # Make sure this is set to False - self._is_overriden = False - self._state = None - self._child_state = None - for item in self.input_fields: - if override_value is None: - child_value = None - else: - child_value = override_value.get(item.key) - - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden - ) - ) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - - # TODO update items - if item is not None: - for _item in self.input_fields: - if _item is not item: - _item.update_style() - - self.value_changed.emit(self) - - self.update_style() - - def update_style(self, is_overriden=None): - child_modified = self.child_modified - child_state = self.style_state(self.child_overriden, child_modified) - if child_state: - child_state = "child-{}".format(child_state) - - if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) - self._child_state = child_state - - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - self.button_toggle_text.setProperty("state", state) - self.button_toggle_text.style().polish(self.button_toggle_text) - - self._state = state - - @property - def is_modified(self): - if self.is_group: - return self.child_modified - return False - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return {self.key: self.item_value()} - - @property - def is_overidable(self): - return self._parent.is_overidable - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - item = klass( - child_configuration, values, self.keys, self - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addWidget(item) - - self.input_fields.append(item) - return item - - def override_values(self): - if not self.is_overriden and not self.child_overriden: - return - - value = {} - for item in self.input_fields: - if hasattr(item, "override_values"): - print("*** HAVE `override_values`", item) - print(item.override_values()) - else: - print("*** missing `override_values`", item) - - if not value: - return - - output = { - "is_group": self.is_group, - "value": value - } - return output - - -class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): - # TODO is not overridable by itself - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - self.any_parent_is_group = any_parent_is_group - - self._is_overriden = False - self.is_modified = False - self.is_group = is_group - - super(DictInvisible, self).__init__(parent) - self.setObjectName("DictInvisible") - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - self.input_fields = [] - - if "key" not in input_data: - print(json.dumps(input_data, indent=4)) - - self.key = input_data["key"] - self.keys = list(parent_keys) - self.keys.append(self.key) - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def update_style(self, *args, **kwargs): - return - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return {self.key: self.item_value()} - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - if item_type == "schema": - for _schema in child_configuration["children"]: - children = config.gui_schema(_schema) - self.add_children_gui(children, values) - return - - klass = TypeToKlass.types.get(item_type) - item = klass( - child_configuration, values, self.keys, self - ) - self.layout().addWidget(item) - - item.value_changed.connect(self._on_value_change) - - self.input_fields.append(item) - return item - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - # TODO update items - if item is not None: - is_overriden = self.is_overriden - for _item in self.input_fields: - if _item is not item: - _item.update_style(is_overriden) - - self.value_changed.emit(self) - - def apply_overrides(self, override_value): - self._is_overriden = False - for item in self.input_fields: - if override_value is None: - child_value = None - else: - child_value = override_value.get(item.key) - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden - ) - ) - self.update_style() - - def override_values(self): - if not self.is_overriden and not self.child_overriden: - return - - value = {} - for item in self.input_fields: - if hasattr(item, "override_values"): - print("*** HAVE `override_values`", item) - print(item.override_values()) - else: - print("*** missing `override_values`", item) - - if not value: - return - - output = { - "is_group": self.is_group, - "value": value - } - return output - - -class DictFormWidget(QtWidgets.QWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self.any_parent_is_group = any_parent_is_group - - self.is_modified = False - self.is_overriden = False - self.is_group = False - - super(DictFormWidget, self).__init__(parent) - - self.input_fields = {} - self.content_layout = QtWidgets.QFormLayout(self) - - self.keys = list(parent_keys) - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - self.value_changed.emit(self) - - def item_value(self): - output = {} - for input_field in self.input_fields.values(): - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - @property - def child_modified(self): - for input_field in self.input_fields.values(): - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields.values(): - if input_field.child_overriden: - return True - return False - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def config_value(self): - return self.item_value() - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - key = child_configuration["key"] - # Pop label to not be set in child - label = child_configuration["label"] - - klass = TypeToKlass.types.get(item_type) - - label_widget = QtWidgets.QLabel(label) - - item = klass( - child_configuration, values, self.keys, self, label_widget - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addRow(label_widget, item) - self.input_fields[key] = item - return item - - -class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): - _btn_size = 20 - value_changed = QtCore.Signal(object) - - def __init__(self, object_type, parent): - self._parent = parent - - super(ModifiableDictItem, self).__init__(parent) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(3) - - ItemKlass = TypeToKlass.types[object_type] - - self.key_input = QtWidgets.QLineEdit() - self.key_input.setObjectName("DictKey") - - self.value_input = ItemKlass( - {}, - AS_WIDGET, - [], - self, - None - ) - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") - - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") - - layout.addWidget(self.key_input, 0) - layout.addWidget(self.value_input, 1) - layout.addWidget(self.add_btn, 0) - layout.addWidget(self.remove_btn, 0) - - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) - - self.key_input.textChanged.connect(self._on_value_change) - self.value_input.value_changed.connect(self._on_value_change) - - self.default_key = self._key() - self.default_value = self.value_input.item_value() - - self.override_key = None - self.override_value = None - - self.is_single = False - - def _key(self): - return self.key_input.text() - - def _on_value_change(self, item=None): - self.update_style() - self.value_changed.emit(self) - - @property - def is_group(self): - return self._parent.is_group - - @property - def any_parent_is_group(self): - return self._parent.any_parent_is_group - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def is_key_modified(self): - return self._key() != self.default_key - - def is_value_modified(self): - return self.value_input.is_modified - - @property - def is_modified(self): - return self.is_value_modified() or self.is_key_modified() - - def update_style(self): - if self.is_key_modified(): - state = "modified" - else: - state = "" - - self.key_input.setProperty("state", state) - self.key_input.style().polish(self.key_input) - - def row(self): - return self.parent().input_fields.index(self) - - def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) - - def on_remove_clicked(self): - if self.is_single: - self.value_input.clear_value() - self.key_input.setText("") - else: - self.parent().remove_row(self) - - def config_value(self): - key = self.key_input.text() - value = self.value_input.item_value() - if not key: - return {} - return {key: value} - - -class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__(self, input_data, values, parent_keys, parent): - self._parent = parent - - super(ModifiableDictSubWidget, self).__init__(parent) - self.setObjectName("ModifiableDictSubWidget") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(5) - self.setLayout(layout) - - self.input_fields = [] - self.object_type = input_data["object_type"] - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - value = self.value_from_values(values) - if value is not NOT_SET: - for item_key, item_value in value.items(): - self.add_row(key=item_key, value=item_value) - - if self.count() == 0: - self.add_row() - - self.default_value = self.config_value() - self.override_value = None - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._parent.is_overriden - - @property - def is_group(self): - return self._parent.is_group - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - @property - def any_parent_is_group(self): - return self._parent.any_parent_is_group - - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def count(self): - return len(self.input_fields) - - def add_row(self, row=None, key=None, value=None): - # Create new item - item_widget = ModifiableDictItem(self.object_type, self) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False - - item_widget.value_changed.connect(self._on_value_change) - - if row is None: - self.layout().addWidget(item_widget) - self.input_fields.append(item_widget) - else: - self.layout().insertWidget(row, item_widget) - self.input_fields.insert(row, item_widget) - - # Set value if entered value is not None - # else (when add button clicked) trigger `_on_value_change` - if value is not None and key is not None: - item_widget.default_key = key - item_widget.key_input.setText(key) - item_widget.value_input.set_value(value, default_value=True) - else: - self._on_value_change() - self.parent().updateGeometry() - - def remove_row(self, item_widget): - item_widget.value_changed.disconnect() - - self.layout().removeWidget(item_widget) - self.input_fields.remove(item_widget) - item_widget.setParent(None) - item_widget.deleteLater() - - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True - - self._on_value_change() - self.parent().updateGeometry() - - def config_value(self): - output = {} - for item in self.input_fields: - item_value = item.config_value() - if item_value: - output.update(item_value) - return output - - -class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): - # Should be used only for dictionary with one datatype as value - # TODO this is actually input field (do not care if is group or not) - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, - label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.any_parent_is_group = any_parent_is_group - - self.is_group = is_group - self._is_modified = False - self._is_overriden = False - self._was_overriden = False - self._state = None - - super(ModifiableDict, self).__init__(input_data["label"], parent) - self.setObjectName("ModifiableDict") - - self.value_widget = ModifiableDictSubWidget( - input_data, values, parent_keys, self - ) - self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - self.value_widget.value_changed.connect(self._on_value_change) - - self.set_content_widget(self.value_widget) - - self.key = input_data["key"] - - self.default_value = self.item_value() - self.override_value = None - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self.is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.default_value - - self.value_changed.emit(self) - - self.update_style() - - @property - def child_modified(self): - return self.is_modified - - @property - def is_modified(self): - return self._is_modified - - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def is_modified(self): - return self._is_modified - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def apply_overrides(self, override_value): - self._state = None - self._is_modified = False - self.override_value = override_value - if override_value is None: - self._is_overriden = False - self._was_overriden = False - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - self.update_style() - - def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - if state: - child_state = "child-{}".format(state) - else: - child_state = "" - - self.setProperty("state", child_state) - self.style().polish(self) - - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - self._state = state - - def item_value(self): - return self.value_widget.config_value() - - def config_value(self): - return {self.key: self.item_value()} - - def override_value(self): - return - - -TypeToKlass.types["boolean"] = BooleanWidget -TypeToKlass.types["text-singleline"] = TextSingleLineWidget -TypeToKlass.types["text-multiline"] = TextMultiLineWidget -TypeToKlass.types["raw-json"] = RawJsonWidget -TypeToKlass.types["int"] = IntegerWidget -TypeToKlass.types["float"] = FloatWidget -TypeToKlass.types["dict-expanding"] = DictExpandWidget -TypeToKlass.types["dict-form"] = DictFormWidget -TypeToKlass.types["dict-invisible"] = DictInvisible -TypeToKlass.types["dict-modifiable"] = ModifiableDict -TypeToKlass.types["list-text"] = TextListWidget From cdda1babc33e47b3b69877e1e1fb1926680e2325 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 00:14:16 +0200 Subject: [PATCH 388/947] removed buttons from svg --- .../standalonepublish/widgets/__init__.py | 1 - .../widgets/button_from_svgs.py | 113 ------------------ 2 files changed, 114 deletions(-) delete mode 100644 pype/tools/standalonepublish/widgets/button_from_svgs.py diff --git a/pype/tools/standalonepublish/widgets/__init__.py b/pype/tools/standalonepublish/widgets/__init__.py index 0db3643cf3..11da0aa1c9 100644 --- a/pype/tools/standalonepublish/widgets/__init__.py +++ b/pype/tools/standalonepublish/widgets/__init__.py @@ -9,7 +9,6 @@ PluginRole = QtCore.Qt.UserRole + 5 PluginKeyRole = QtCore.Qt.UserRole + 6 from pype.resources import get_resource -from .button_from_svgs import SvgResizable, SvgButton from .model_node import Node from .model_tree import TreeModel diff --git a/pype/tools/standalonepublish/widgets/button_from_svgs.py b/pype/tools/standalonepublish/widgets/button_from_svgs.py deleted file mode 100644 index 4255c5f29b..0000000000 --- a/pype/tools/standalonepublish/widgets/button_from_svgs.py +++ /dev/null @@ -1,113 +0,0 @@ -from xml.dom import minidom - -from . import QtGui, QtCore, QtWidgets -from PyQt5 import QtSvg, QtXml - - -class SvgResizable(QtSvg.QSvgWidget): - clicked = QtCore.Signal() - - def __init__(self, filepath, width=None, height=None, fill=None): - super().__init__() - self.xmldoc = minidom.parse(filepath) - itemlist = self.xmldoc.getElementsByTagName('svg') - for element in itemlist: - if fill: - element.setAttribute('fill', str(fill)) - # TODO auto scale if only one is set - if width is not None and height is not None: - self.setMaximumSize(width, height) - self.setMinimumSize(width, height) - xml_string = self.xmldoc.toxml() - svg_bytes = bytearray(xml_string, encoding='utf-8') - - self.load(svg_bytes) - - def change_color(self, color): - element = self.xmldoc.getElementsByTagName('svg')[0] - element.setAttribute('fill', str(color)) - xml_string = self.xmldoc.toxml() - svg_bytes = bytearray(xml_string, encoding='utf-8') - self.load(svg_bytes) - - def mousePressEvent(self, event): - self.clicked.emit() - - -class SvgButton(QtWidgets.QFrame): - clicked = QtCore.Signal() - def __init__( - self, filepath, width=None, height=None, fills=[], - parent=None, checkable=True - ): - super().__init__(parent) - self.checkable = checkable - self.checked = False - - xmldoc = minidom.parse(filepath) - element = xmldoc.getElementsByTagName('svg')[0] - c_actual = '#777777' - if element.hasAttribute('fill'): - c_actual = element.getAttribute('fill') - self.store_fills(fills, c_actual) - - self.installEventFilter(self) - self.svg_widget = SvgResizable(filepath, width, height, self.c_normal) - xmldoc = minidom.parse(filepath) - - layout = QtWidgets.QHBoxLayout(self) - layout.setSpacing(0) - layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.svg_widget) - - if width is not None and height is not None: - self.setMaximumSize(width, height) - self.setMinimumSize(width, height) - - def store_fills(self, fills, actual): - if len(fills) == 0: - fills = [actual, actual, actual, actual] - elif len(fills) == 1: - fills = [fills[0], fills[0], fills[0], fills[0]] - elif len(fills) == 2: - fills = [fills[0], fills[1], fills[1], fills[1]] - elif len(fills) == 3: - fills = [fills[0], fills[1], fills[2], fills[2]] - self.c_normal = fills[0] - self.c_hover = fills[1] - self.c_active = fills[2] - self.c_active_hover = fills[3] - - def eventFilter(self, object, event): - if event.type() == QtCore.QEvent.Enter: - self.hoverEnterEvent(event) - return True - elif event.type() == QtCore.QEvent.Leave: - self.hoverLeaveEvent(event) - return True - elif event.type() == QtCore.QEvent.MouseButtonRelease: - self.mousePressEvent(event) - return False - - def change_checked(self, hover=True): - if self.checkable: - self.checked = not self.checked - if hover: - self.hoverEnterEvent() - else: - self.hoverLeaveEvent() - - def hoverEnterEvent(self, event=None): - color = self.c_hover - if self.checked: - color = self.c_active_hover - self.svg_widget.change_color(color) - - def hoverLeaveEvent(self, event=None): - color = self.c_normal - if self.checked: - color = self.c_active - self.svg_widget.change_color(color) - - def mousePressEvent(self, event=None): - self.clicked.emit() From 11032200c595ffdfb9bf11f2c301bd2f062dded2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 00:16:51 +0200 Subject: [PATCH 389/947] cleaned imports --- .../tools/standalonepublish/widgets/__init__.py | 6 +----- .../standalonepublish/widgets/model_asset.py | 5 +++-- .../widgets/model_filter_proxy_exact_match.py | 2 +- .../model_filter_proxy_recursive_sort.py | 2 +- .../widgets/model_tasks_template.py | 7 ++++--- .../standalonepublish/widgets/model_tree.py | 2 +- .../widgets/model_tree_view_deselectable.py | 2 +- .../standalonepublish/widgets/widget_asset.py | 5 +++-- .../widgets/widget_component_item.py | 4 ++-- .../widgets/widget_components.py | 1 - .../widgets/widget_components_list.py | 2 +- .../widgets/widget_drop_empty.py | 17 ++++++++--------- .../widgets/widget_drop_frame.py | 3 +-- .../standalonepublish/widgets/widget_family.py | 6 +----- .../widgets/widget_family_desc.py | 12 +++--------- .../standalonepublish/widgets/widget_shadow.py | 8 +++++--- 16 files changed, 36 insertions(+), 48 deletions(-) diff --git a/pype/tools/standalonepublish/widgets/__init__.py b/pype/tools/standalonepublish/widgets/__init__.py index 11da0aa1c9..e61897f807 100644 --- a/pype/tools/standalonepublish/widgets/__init__.py +++ b/pype/tools/standalonepublish/widgets/__init__.py @@ -1,6 +1,4 @@ -from avalon.vendor.Qt import * -from avalon.vendor import qtawesome -from avalon import style +from Qt import QtCore HelpRole = QtCore.Qt.UserRole + 2 FamilyRole = QtCore.Qt.UserRole + 3 @@ -8,8 +6,6 @@ ExistsRole = QtCore.Qt.UserRole + 4 PluginRole = QtCore.Qt.UserRole + 5 PluginKeyRole = QtCore.Qt.UserRole + 6 -from pype.resources import get_resource - from .model_node import Node from .model_tree import TreeModel from .model_asset import AssetModel, _iter_model_rows diff --git a/pype/tools/standalonepublish/widgets/model_asset.py b/pype/tools/standalonepublish/widgets/model_asset.py index 6bea35ebd7..44649b3dc3 100644 --- a/pype/tools/standalonepublish/widgets/model_asset.py +++ b/pype/tools/standalonepublish/widgets/model_asset.py @@ -1,8 +1,9 @@ import logging import collections -from . import QtCore, QtGui +from Qt import QtCore, QtGui from . import TreeModel, Node -from . import style, qtawesome +from avalon.vendor import qtawesome +from avalon import style log = logging.getLogger(__name__) diff --git a/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py b/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py index 862e4071db..604ae30934 100644 --- a/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py +++ b/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py @@ -1,4 +1,4 @@ -from . import QtCore +from Qt import QtCore class ExactMatchesFilterProxyModel(QtCore.QSortFilterProxyModel): diff --git a/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py b/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py index 9528e96ebf..71ecdf41dc 100644 --- a/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py +++ b/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py @@ -1,4 +1,4 @@ -from . import QtCore +from Qt import QtCore import re diff --git a/pype/tools/standalonepublish/widgets/model_tasks_template.py b/pype/tools/standalonepublish/widgets/model_tasks_template.py index 336921b37a..476f45391d 100644 --- a/pype/tools/standalonepublish/widgets/model_tasks_template.py +++ b/pype/tools/standalonepublish/widgets/model_tasks_template.py @@ -1,6 +1,7 @@ -from . import QtCore, TreeModel -from . import Node -from . import qtawesome, style +from Qt import QtCore +from . import Node, TreeModel +from avalon.vendor import qtawesome +from avalon import style class TasksTemplateModel(TreeModel): diff --git a/pype/tools/standalonepublish/widgets/model_tree.py b/pype/tools/standalonepublish/widgets/model_tree.py index f37b7a00b2..efac0d6b78 100644 --- a/pype/tools/standalonepublish/widgets/model_tree.py +++ b/pype/tools/standalonepublish/widgets/model_tree.py @@ -1,4 +1,4 @@ -from . import QtCore +from Qt import QtCore from . import Node diff --git a/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py b/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py index 78bec44d36..6a15916981 100644 --- a/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py +++ b/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py @@ -1,4 +1,4 @@ -from . import QtWidgets, QtCore +from Qt import QtWidgets, QtCore class DeselectableTreeView(QtWidgets.QTreeView): diff --git a/pype/tools/standalonepublish/widgets/widget_asset.py b/pype/tools/standalonepublish/widgets/widget_asset.py index d9241bd91f..c468c9627b 100644 --- a/pype/tools/standalonepublish/widgets/widget_asset.py +++ b/pype/tools/standalonepublish/widgets/widget_asset.py @@ -1,7 +1,8 @@ import contextlib -from . import QtWidgets, QtCore +from Qt import QtWidgets, QtCore from . import RecursiveSortFilterProxyModel, AssetModel -from . import qtawesome, style +from avalon.vendor import qtawesome +from avalon import style from . import TasksTemplateModel, DeselectableTreeView from . import _iter_model_rows diff --git a/pype/tools/standalonepublish/widgets/widget_component_item.py b/pype/tools/standalonepublish/widgets/widget_component_item.py index 40298520b1..dd838075e3 100644 --- a/pype/tools/standalonepublish/widgets/widget_component_item.py +++ b/pype/tools/standalonepublish/widgets/widget_component_item.py @@ -1,6 +1,6 @@ import os -from . import QtCore, QtGui, QtWidgets -from . import get_resource +from Qt import QtCore, QtGui, QtWidgets +from pype.resources import get_resource from avalon import style diff --git a/pype/tools/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py index 3b6c326af0..7e0327f00a 100644 --- a/pype/tools/standalonepublish/widgets/widget_components.py +++ b/pype/tools/standalonepublish/widgets/widget_components.py @@ -7,7 +7,6 @@ import string from Qt import QtWidgets, QtCore from . import DropDataFrame -from avalon.tools import publish from avalon import io from pype.api import execute, Logger diff --git a/pype/tools/standalonepublish/widgets/widget_components_list.py b/pype/tools/standalonepublish/widgets/widget_components_list.py index f85e9f0aa6..4e502a2e5f 100644 --- a/pype/tools/standalonepublish/widgets/widget_components_list.py +++ b/pype/tools/standalonepublish/widgets/widget_components_list.py @@ -1,4 +1,4 @@ -from . import QtCore, QtGui, QtWidgets +from Qt import QtWidgets class ComponentsList(QtWidgets.QTableWidget): diff --git a/pype/tools/standalonepublish/widgets/widget_drop_empty.py b/pype/tools/standalonepublish/widgets/widget_drop_empty.py index d352e70355..ed526f2a78 100644 --- a/pype/tools/standalonepublish/widgets/widget_drop_empty.py +++ b/pype/tools/standalonepublish/widgets/widget_drop_empty.py @@ -1,7 +1,4 @@ -import os -import logging -import clique -from . import QtWidgets, QtCore, QtGui +from Qt import QtWidgets, QtCore, QtGui class DropEmpty(QtWidgets.QWidget): @@ -42,11 +39,13 @@ class DropEmpty(QtWidgets.QWidget): super().paintEvent(event) painter = QtGui.QPainter(self) pen = QtGui.QPen() - pen.setWidth(1); - pen.setBrush(QtCore.Qt.darkGray); - pen.setStyle(QtCore.Qt.DashLine); + pen.setWidth(1) + pen.setBrush(QtCore.Qt.darkGray) + pen.setStyle(QtCore.Qt.DashLine) painter.setPen(pen) painter.drawRect( - 10, 10, - self.rect().width()-15, self.rect().height()-15 + 10, + 10, + self.rect().width() - 15, + self.rect().height() - 15 ) diff --git a/pype/tools/standalonepublish/widgets/widget_drop_frame.py b/pype/tools/standalonepublish/widgets/widget_drop_frame.py index 37d22cf887..e13f701b30 100644 --- a/pype/tools/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/tools/standalonepublish/widgets/widget_drop_frame.py @@ -3,9 +3,8 @@ import re import json import clique import subprocess -from pype.api import config import pype.lib -from . import QtWidgets, QtCore +from Qt import QtWidgets, QtCore from . import DropEmpty, ComponentsList, ComponentItem diff --git a/pype/tools/standalonepublish/widgets/widget_family.py b/pype/tools/standalonepublish/widgets/widget_family.py index 29a0812a91..1c8f2238fc 100644 --- a/pype/tools/standalonepublish/widgets/widget_family.py +++ b/pype/tools/standalonepublish/widgets/widget_family.py @@ -1,10 +1,6 @@ -import os -import sys -import inspect -import json from collections import namedtuple -from . import QtWidgets, QtCore +from Qt import QtWidgets, QtCore from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget diff --git a/pype/tools/standalonepublish/widgets/widget_family_desc.py b/pype/tools/standalonepublish/widgets/widget_family_desc.py index 7c80dcfd57..8c95ddf2e4 100644 --- a/pype/tools/standalonepublish/widgets/widget_family_desc.py +++ b/pype/tools/standalonepublish/widgets/widget_family_desc.py @@ -1,13 +1,7 @@ -import os -import sys -import inspect -import json - -from . import QtWidgets, QtCore, QtGui -from . import HelpRole, FamilyRole, ExistsRole, PluginRole -from . import qtawesome +from Qt import QtWidgets, QtCore, QtGui +from . import FamilyRole, PluginRole +from avalon.vendor import qtawesome import six -from pype import lib as pypelib class FamilyDescriptionWidget(QtWidgets.QWidget): diff --git a/pype/tools/standalonepublish/widgets/widget_shadow.py b/pype/tools/standalonepublish/widgets/widget_shadow.py index 1bb9cee44b..de5fdf6be0 100644 --- a/pype/tools/standalonepublish/widgets/widget_shadow.py +++ b/pype/tools/standalonepublish/widgets/widget_shadow.py @@ -1,4 +1,4 @@ -from . import QtWidgets, QtCore, QtGui +from Qt import QtWidgets, QtCore, QtGui class ShadowWidget(QtWidgets.QWidget): @@ -26,7 +26,9 @@ class ShadowWidget(QtWidgets.QWidget): painter.begin(self) painter.setFont(self.font) painter.setRenderHint(QtGui.QPainter.Antialiasing) - painter.fillRect(event.rect(), QtGui.QBrush(QtGui.QColor(0, 0, 0, 127))) + painter.fillRect( + event.rect(), QtGui.QBrush(QtGui.QColor(0, 0, 0, 127)) + ) painter.drawText( QtCore.QRectF( 0.0, @@ -34,7 +36,7 @@ class ShadowWidget(QtWidgets.QWidget): self.parent_widget.frameGeometry().width(), self.parent_widget.frameGeometry().height() ), - QtCore.Qt.AlignCenter|QtCore.Qt.AlignCenter, + QtCore.Qt.AlignCenter | QtCore.Qt.AlignCenter, self.message ) painter.end() From f4a14bbc109923852768f46d61a73b1575d597a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 00:17:50 +0200 Subject: [PATCH 390/947] one more imports cleanup --- pype/tools/standalonepublish/__main__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py index 5bcf514994..aba8e6c0a4 100644 --- a/pype/tools/standalonepublish/__main__.py +++ b/pype/tools/standalonepublish/__main__.py @@ -4,8 +4,6 @@ import app import signal from Qt import QtWidgets from avalon import style -import pype -import pyblish.api if __name__ == "__main__": From 62160ab368237ba013cb35052ddb95a242f2aed8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 00:37:09 +0200 Subject: [PATCH 391/947] replaced api.Session with io.Session --- pype/tools/launcher/app.py | 10 +++++----- pype/tools/launcher/models.py | 1 - pype/tools/launcher/widgets.py | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index 78c5406fa8..ef00880585 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -2,7 +2,7 @@ import sys import copy from avalon.vendor.Qt import QtWidgets, QtCore, QtGui -from avalon import io, api, style +from avalon import io, style from avalon.tools import lib as tools_lib from avalon.tools.widgets import AssetWidget @@ -197,7 +197,7 @@ class AssetsPanel(QtWidgets.QWidget): def on_project_changed(self): project_name = self.project_bar.get_current_project() - api.Session["AVALON_PROJECT"] = project_name + io.Session["AVALON_PROJECT"] = project_name self.assets_widget.refresh() # Force asset change callback to ensure tasks are correctly reset @@ -220,7 +220,7 @@ class AssetsPanel(QtWidgets.QWidget): def get_current_session(self): asset_doc = self.assets_widget.get_active_asset_document() - session = copy.deepcopy(api.Session) + session = copy.deepcopy(io.Session) # Clear some values that we are about to collect if available session.pop("AVALON_SILO", None) @@ -407,7 +407,7 @@ class Window(QtWidgets.QDialog): # Assets page return self.asset_panel.get_current_session() - session = copy.deepcopy(api.Session) + session = copy.deepcopy(io.Session) # Remove some potential invalid session values # that we know are not set when not browsing in @@ -664,7 +664,7 @@ def cli(args): actions.register_environment_actions() io.install() - #api.Session["AVALON_PROJECT"] = project + #io.Session["AVALON_PROJECT"] = project import traceback sys.excepthook = lambda typ, val, tb: traceback.print_last() diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index ce6e0c722e..b78bff950b 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -1,4 +1,3 @@ -import os import copy import logging import collections diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index a264466dbc..9bcd9000d1 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -1,7 +1,7 @@ import copy from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome -from avalon import api +from avalon import io from .delegates import ActionDelegate from .models import TaskModel, ActionModel, ProjectModel @@ -37,7 +37,7 @@ class ProjectBar(QtWidgets.QWidget): self.project_combobox.currentIndexChanged.connect(self.project_changed) # Set current project by default if it's set. - project_name = api.Session.get("AVALON_PROJECT") + project_name = io.Session.get("AVALON_PROJECT") if project_name: self.set_project(project_name) From 2bae09f4e024936017444fe004f32a5fac7f294a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 01:10:25 +0200 Subject: [PATCH 392/947] using custom dbconnector instead of avalon.io --- pype/tools/launcher/app.py | 48 ++++++++++++++++++++-------------- pype/tools/launcher/lib.py | 8 +----- pype/tools/launcher/models.py | 28 +++++++++++++------- pype/tools/launcher/widgets.py | 25 +++++++++++------- 4 files changed, 63 insertions(+), 46 deletions(-) diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index ef00880585..6f554110dc 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -2,7 +2,9 @@ import sys import copy from avalon.vendor.Qt import QtWidgets, QtCore, QtGui -from avalon import io, style +from avalon import style + +from pype.modules.ftrack.lib.io_nonsingleton import DbConnector from avalon.tools import lib as tools_lib from avalon.tools.widgets import AssetWidget @@ -86,17 +88,19 @@ class ProjectsPanel(QtWidgets.QWidget): project_clicked = QtCore.Signal(str) - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(ProjectsPanel, self).__init__(parent=parent) layout = QtWidgets.QVBoxLayout(self) - io.install() + self.dbcon = dbcon + self.dbcon.install() + view = IconListView(parent=self) view.setSelectionMode(QtWidgets.QListView.NoSelection) flick = FlickCharm(parent=self) flick.activateOn(view) - model = ProjectModel() + model = ProjectModel(self.dbcon) model.hide_invisible = True model.refresh() view.setModel(model) @@ -118,31 +122,35 @@ class AssetsPanel(QtWidgets.QWidget): """Assets page""" back_clicked = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(AssetsPanel, self).__init__(parent=parent) + self.dbcon = dbcon + # project bar - project_bar_widget = QtWidgets.QWidget() + project_bar_widget = QtWidgets.QWidget(self) layout = QtWidgets.QHBoxLayout(project_bar_widget) layout.setSpacing(4) btn_back_icon = qtawesome.icon("fa.angle-left", color="white") - btn_back = QtWidgets.QPushButton() + btn_back = QtWidgets.QPushButton(project_bar_widget) btn_back.setIcon(btn_back_icon) btn_back.setFixedWidth(23) btn_back.setFixedHeight(23) - project_bar = ProjectBar() + project_bar = ProjectBar(self.dbcon, project_bar_widget) layout.addWidget(btn_back) layout.addWidget(project_bar) # assets - assets_proxy_widgets = QtWidgets.QWidget() + assets_proxy_widgets = QtWidgets.QWidget(self) assets_proxy_widgets.setContentsMargins(0, 0, 0, 0) assets_layout = QtWidgets.QVBoxLayout(assets_proxy_widgets) - assets_widget = AssetWidget() + assets_widget = AssetWidget( + dbcon=self.dbcon, parent=assets_proxy_widgets + ) # Make assets view flickable flick = FlickCharm(parent=self) @@ -153,7 +161,7 @@ class AssetsPanel(QtWidgets.QWidget): assets_layout.addWidget(assets_widget) # tasks - tasks_widget = TasksWidget() + tasks_widget = TasksWidget(self.dbcon, self) body = QtWidgets.QSplitter() body.setContentsMargins(0, 0, 0, 0) body.setSizePolicy( @@ -197,7 +205,7 @@ class AssetsPanel(QtWidgets.QWidget): def on_project_changed(self): project_name = self.project_bar.get_current_project() - io.Session["AVALON_PROJECT"] = project_name + self.dbcon.Session["AVALON_PROJECT"] = project_name self.assets_widget.refresh() # Force asset change callback to ensure tasks are correctly reset @@ -220,7 +228,7 @@ class AssetsPanel(QtWidgets.QWidget): def get_current_session(self): asset_doc = self.assets_widget.get_active_asset_document() - session = copy.deepcopy(io.Session) + session = copy.deepcopy(self.dbcon.Session) # Clear some values that we are about to collect if available session.pop("AVALON_SILO", None) @@ -242,6 +250,8 @@ class Window(QtWidgets.QDialog): def __init__(self, parent=None): super(Window, self).__init__(parent) + self.dbcon = DbConnector() + self.setWindowTitle("Launcher") self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -251,15 +261,15 @@ class Window(QtWidgets.QDialog): self.windowFlags() | QtCore.Qt.WindowMinimizeButtonHint ) - project_panel = ProjectsPanel() - asset_panel = AssetsPanel() + project_panel = ProjectsPanel(self.dbcon) + asset_panel = AssetsPanel(self.dbcon) page_slider = SlidePageWidget() page_slider.addWidget(project_panel) page_slider.addWidget(asset_panel) # actions - actions_bar = ActionBar() + actions_bar = ActionBar(self.dbcon, self) # statusbar statusbar = QtWidgets.QWidget() @@ -356,7 +366,7 @@ class Window(QtWidgets.QDialog): def on_project_changed(self): project_name = self.asset_panel.project_bar.get_current_project() - io.Session["AVALON_PROJECT"] = project_name + self.dbcon.Session["AVALON_PROJECT"] = project_name # Update the Action plug-ins available for the current project self.actions_bar.model.discover() @@ -368,7 +378,7 @@ class Window(QtWidgets.QDialog): tools_lib.schedule(self.on_refresh_actions, delay) def on_project_clicked(self, project_name): - io.Session["AVALON_PROJECT"] = project_name + self.dbcon.Session["AVALON_PROJECT"] = project_name # Refresh projects self.asset_panel.project_bar.refresh() self.asset_panel.set_project(project_name) @@ -407,7 +417,7 @@ class Window(QtWidgets.QDialog): # Assets page return self.asset_panel.get_current_session() - session = copy.deepcopy(io.Session) + session = copy.deepcopy(self.dbcon.Session) # Remove some potential invalid session values # that we know are not set when not browsing in diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index e7933e9843..0bbbb55560 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -16,7 +16,7 @@ provides a bridge between the file-based project inventory and configuration. import os from Qt import QtGui -from avalon import io, lib, pipeline +from avalon import lib, pipeline from avalon.vendor import qtawesome from pype.api import resources @@ -24,12 +24,6 @@ ICON_CACHE = {} NOT_FOUND = type("NotFound", (object, ), {}) -def list_project_tasks(): - """List the project task types available in the current project""" - project = io.find_one({"type": "project"}) - return [task["name"] for task in project["config"]["tasks"]] - - def get_application_actions(project): """Define dynamic Application classes for project using `.toml` files diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index b78bff950b..61e240c2eb 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -5,7 +5,7 @@ import collections from . import lib from Qt import QtCore, QtGui from avalon.vendor import qtawesome -from avalon import io, style, api +from avalon import style, api log = logging.getLogger(__name__) @@ -13,8 +13,10 @@ log = logging.getLogger(__name__) class TaskModel(QtGui.QStandardItemModel): """A model listing the tasks combined for a list of assets""" - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(TaskModel, self).__init__(parent=parent) + self.dbcon = dbcon + self._num_assets = 0 self.default_icon = qtawesome.icon( @@ -29,11 +31,11 @@ class TaskModel(QtGui.QStandardItemModel): self._get_task_icons() def _get_task_icons(self): - if io.Session.get("AVALON_PROJECT") is None: + if not self.dbcon.Session.get("AVALON_PROJECT"): return # Get the project configured icons from database - project = io.find_one({"type": "project"}) + project = self.dbcon.find_one({"type": "project"}) for task in project["config"].get("tasks") or []: icon_name = task.get("icon") if icon_name: @@ -52,7 +54,7 @@ class TaskModel(QtGui.QStandardItemModel): if asset_docs is None and asset_ids is not None: # find assets in db by query - asset_docs = list(io.find({ + asset_docs = list(self.dbcon.find({ "type": "asset", "_id": {"$in": asset_ids} })) @@ -108,8 +110,10 @@ class ActionModel(QtGui.QStandardItemModel): ACTION_ROLE = QtCore.Qt.UserRole GROUP_ROLE = QtCore.Qt.UserRole + 1 - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(ActionModel, self).__init__(parent=parent) + self.dbcon = dbcon + self._session = {} self._groups = {} self.default_icon = qtawesome.icon("fa.cube", color="white") @@ -120,7 +124,7 @@ class ActionModel(QtGui.QStandardItemModel): def discover(self): """Set up Actions cache. Run this for each new project.""" - if not io.Session.get("AVALON_PROJECT"): + if not self.dbcon.Session.get("AVALON_PROJECT"): self._registered_actions = list() return @@ -128,7 +132,7 @@ class ActionModel(QtGui.QStandardItemModel): actions = api.discover(api.Action) # Get available project actions and the application actions - project_doc = io.find_one({"type": "project"}) + project_doc = self.dbcon.find_one({"type": "project"}) app_actions = lib.get_application_actions(project_doc) actions.extend(app_actions) @@ -233,9 +237,11 @@ class ActionModel(QtGui.QStandardItemModel): class ProjectModel(QtGui.QStandardItemModel): """List of projects""" - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(ProjectModel, self).__init__(parent=parent) + self.dbcon = dbcon + self.hide_invisible = False self.project_icon = qtawesome.icon("fa.map", color="white") @@ -251,7 +257,9 @@ class ProjectModel(QtGui.QStandardItemModel): def get_projects(self): project_docs = [] - for project_doc in sorted(io.projects(), key=lambda x: x["name"]): + for project_doc in sorted( + self.dbcon.projects(), key=lambda x: x["name"] + ): if ( self.hide_invisible and not project_doc["data"].get("visible", True) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 9bcd9000d1..4fc7d166cb 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -1,7 +1,6 @@ import copy from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome -from avalon import io from .delegates import ActionDelegate from .models import TaskModel, ActionModel, ProjectModel @@ -11,10 +10,12 @@ from .flickcharm import FlickCharm class ProjectBar(QtWidgets.QWidget): project_changed = QtCore.Signal(int) - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(ProjectBar, self).__init__(parent) - self.model = ProjectModel() + self.dbcon = dbcon + + self.model = ProjectModel(self.dbcon) self.model.hide_invisible = True self.project_combobox = QtWidgets.QComboBox() @@ -37,7 +38,7 @@ class ProjectBar(QtWidgets.QWidget): self.project_combobox.currentIndexChanged.connect(self.project_changed) # Set current project by default if it's set. - project_name = io.Session.get("AVALON_PROJECT") + project_name = self.dbcon.Session.get("AVALON_PROJECT") if project_name: self.set_project(project_name) @@ -68,9 +69,11 @@ class ActionBar(QtWidgets.QWidget): action_clicked = QtCore.Signal(object) - def __init__(self, parent=None): + def __init__(self, dbcon, parent=None): super(ActionBar, self).__init__(parent) + self.dbcon = dbcon + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(8, 0, 8, 0) @@ -86,7 +89,7 @@ class ActionBar(QtWidgets.QWidget): view.setSpacing(0) view.setWordWrap(True) - model = ActionModel(self) + model = ActionModel(self.dbcon, self) view.setModel(model) delegate = ActionDelegate(model.GROUP_ROLE, self) @@ -140,12 +143,14 @@ class TasksWidget(QtWidgets.QWidget): QtCore.QItemSelectionModel.Select | QtCore.QItemSelectionModel.Rows ) - def __init__(self): - super(TasksWidget, self).__init__() + def __init__(self, dbcon, parent=None): + super(TasksWidget, self).__init__(parent) - view = QtWidgets.QTreeView() + self.dbcon = dbcon + + view = QtWidgets.QTreeView(self) view.setIndentation(0) - model = TaskModel() + model = TaskModel(self.dbcon) view.setModel(model) layout = QtWidgets.QVBoxLayout(self) From 7f7b220d32a689643c341c9a9ea17308e9936c62 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 01:39:18 +0200 Subject: [PATCH 393/947] moved app launching to pype.lib --- pype/lib.py | 176 ++++++++++++++- pype/modules/ftrack/lib/ftrack_app_handler.py | 212 ++---------------- 2 files changed, 188 insertions(+), 200 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index f99cd73e09..ff0c0c0e82 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -7,15 +7,19 @@ import json import collections import logging import itertools +import copy import contextlib import subprocess +import getpass import inspect +import acre +import platform from abc import ABCMeta, abstractmethod from avalon import io, pipeline import six import avalon.api -from .api import config +from .api import config, Anatomy log = logging.getLogger(__name__) @@ -1416,3 +1420,173 @@ def get_latest_version(asset_name, subset_name): assert version, "No version found, this is a bug" return version + + +class ApplicationLaunchFailed(Exception): + pass + + +def launch_application(project_name, asset_name, task_name, app_name): + database = get_avalon_database() + project_document = database[project_name].find_one({"type": "project"}) + asset_document = database[project_name].find_one({ + "type": "asset", + "name": asset_name + }) + + asset_doc_parents = asset_document["data"].get("parents") + hierarchy = "/".join(asset_doc_parents) + + app_def = avalon.lib.get_application(app_name) + app_label = app_def.get("ftrack_label", app_def.get("label", app_name)) + + host_name = app_def["application_dir"] + data = { + "project": { + "name": project_document["name"], + "code": project_document["data"].get("code") + }, + "task": task_name, + "asset": asset_name, + "app": host_name, + "hierarchy": hierarchy + } + + try: + anatomy = Anatomy(project_name) + anatomy_filled = anatomy.format(data) + workdir = os.path.normpath(anatomy_filled["work"]["folder"]) + + except Exception as exc: + raise ApplicationLaunchFailed( + "Error in anatomy.format: {}".format(str(exc)) + ) + + try: + os.makedirs(workdir) + except FileExistsError: + pass + + last_workfile_path = None + extensions = avalon.api.HOST_WORKFILE_EXTENSIONS.get(host_name) + if extensions: + # Find last workfile + file_template = anatomy.templates["work"]["file"] + data.update({ + "version": 1, + "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), + "ext": extensions[0] + }) + + last_workfile_path = avalon.api.last_workfile( + workdir, file_template, data, extensions, True + ) + + # set environments for Avalon + prep_env = copy.deepcopy(os.environ) + prep_env.update({ + "AVALON_PROJECT": project_name, + "AVALON_ASSET": asset_name, + "AVALON_TASK": task_name, + "AVALON_APP": host_name, + "AVALON_APP_NAME": app_name, + "AVALON_HIERARCHY": hierarchy, + "AVALON_WORKDIR": workdir + }) + + start_last_workfile = avalon.api.should_start_last_workfile( + project_name, host_name, task_name + ) + # Store boolean as "0"(False) or "1"(True) + prep_env["AVALON_OPEN_LAST_WORKFILE"] = ( + str(int(bool(start_last_workfile))) + ) + + if ( + start_last_workfile + and last_workfile_path + and os.path.exists(last_workfile_path) + ): + prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path + + prep_env.update(anatomy.roots_obj.root_environments()) + + # collect all the 'environment' attributes from parents + tools_attr = [prep_env["AVALON_APP"], prep_env["AVALON_APP_NAME"]] + tools_env = asset_document["data"].get("tools_env") or [] + tools_attr.extend(tools_env) + + tools_env = acre.get_tools(tools_attr) + env = acre.compute(tools_env) + env = acre.merge(env, current_env=dict(prep_env)) + + # Get path to execute + st_temp_path = os.environ["PYPE_CONFIG"] + os_plat = platform.system().lower() + + # Path to folder with launchers + path = os.path.join(st_temp_path, "launchers", os_plat) + + # Full path to executable launcher + execfile = None + + launch_hook = app_def.get("launch_hook") + if launch_hook: + log.info("launching hook: {}".format(launch_hook)) + ret_val = execute_hook(launch_hook, env=env) + if not ret_val: + raise ApplicationLaunchFailed( + "Hook didn't finish successfully {}".format(app_label) + ) + + if sys.platform == "win32": + for ext in os.environ["PATHEXT"].split(os.pathsep): + fpath = os.path.join(path.strip('"'), app_def["executable"] + ext) + if os.path.isfile(fpath) and os.access(fpath, os.X_OK): + execfile = fpath + break + + # Run SW if was found executable + if execfile is None: + raise ApplicationLaunchFailed( + "We didn't find launcher for {}".format(app_label) + ) + + popen = avalon.lib.launch( + executable=execfile, args=[], environment=env + ) + + elif ( + sys.platform.startswith("linux") + or sys.platform.startswith("darwin") + ): + execfile = os.path.join(path.strip('"'), app_def["executable"]) + # Run SW if was found executable + if execfile is None: + raise ApplicationLaunchFailed( + "We didn't find launcher for {}".format(app_label) + ) + + if not os.path.isfile(execfile): + raise ApplicationLaunchFailed( + "Launcher doesn't exist - {}".format(execfile) + ) + + try: + fp = open(execfile) + except PermissionError as perm_exc: + raise ApplicationLaunchFailed( + "Access denied on launcher {} - {}".format(execfile, perm_exc) + ) + + fp.close() + # check executable permission + if not os.access(execfile, os.X_OK): + raise ApplicationLaunchFailed( + "No executable permission - {}".format(execfile) + ) + + popen = avalon.lib.launch( # noqa: F841 + "/usr/bin/env", args=["bash", execfile], environment=env + ) + return popen diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 22fd6eeaab..4847464973 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -1,16 +1,6 @@ -import os -import sys -import copy -import platform -import avalon.lib -import acre -import getpass from pype import lib as pypelib -from pype.api import config, Anatomy +from pype.api import config from .ftrack_action_handler import BaseAction -from avalon.api import ( - last_workfile, HOST_WORKFILE_EXTENSIONS, should_start_last_workfile -) class AppAction(BaseAction): @@ -156,43 +146,21 @@ class AppAction(BaseAction): entity = entities[0] task_name = entity["name"] - - project_name = entity["project"]["full_name"] - - database = pypelib.get_avalon_database() - asset_name = entity["parent"]["name"] - asset_document = database[project_name].find_one({ - "type": "asset", - "name": asset_name - }) - - hierarchy = "" - asset_doc_parents = asset_document["data"].get("parents") - if asset_doc_parents: - hierarchy = os.path.join(*asset_doc_parents) - - application = avalon.lib.get_application(self.identifier) - host_name = application["application_dir"] - data = { - "project": { - "name": entity["project"]["full_name"], - "code": entity["project"]["name"] - }, - "task": task_name, - "asset": asset_name, - "app": host_name, - "hierarchy": hierarchy - } - + project_name = entity["project"]["full_name"] try: - anatomy = Anatomy(project_name) - anatomy_filled = anatomy.format(data) - workdir = os.path.normpath(anatomy_filled["work"]["folder"]) + pypelib.launch_application(project_name, asset_name, task_name) - except Exception as exc: - msg = "Error in anatomy.format: {}".format( - str(exc) + except pypelib.ApplicationLaunchFailed as exc: + self.log.error(str(exc)) + return { + "success": False, + "message": str(exc) + } + + except Exception: + msg = "Unexpected failure of application launch {}".format( + self.label ) self.log.error(msg, exc_info=True) return { @@ -200,160 +168,6 @@ class AppAction(BaseAction): "message": msg } - try: - os.makedirs(workdir) - except FileExistsError: - pass - - last_workfile_path = None - extensions = HOST_WORKFILE_EXTENSIONS.get(host_name) - if extensions: - # Find last workfile - file_template = anatomy.templates["work"]["file"] - data.update({ - "version": 1, - "user": getpass.getuser(), - "ext": extensions[0] - }) - - last_workfile_path = last_workfile( - workdir, file_template, data, extensions, True - ) - - # set environments for Avalon - prep_env = copy.deepcopy(os.environ) - prep_env.update({ - "AVALON_PROJECT": project_name, - "AVALON_ASSET": asset_name, - "AVALON_TASK": task_name, - "AVALON_APP": host_name, - "AVALON_APP_NAME": self.identifier, - "AVALON_HIERARCHY": hierarchy, - "AVALON_WORKDIR": workdir - }) - - start_last_workfile = should_start_last_workfile( - project_name, host_name, task_name - ) - # Store boolean as "0"(False) or "1"(True) - prep_env["AVALON_OPEN_LAST_WORKFILE"] = ( - str(int(bool(start_last_workfile))) - ) - - if ( - start_last_workfile - and last_workfile_path - and os.path.exists(last_workfile_path) - ): - prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path - - prep_env.update(anatomy.roots_obj.root_environments()) - - # collect all parents from the task - parents = [] - for item in entity['link']: - parents.append(session.get(item['type'], item['id'])) - - # collect all the 'environment' attributes from parents - tools_attr = [prep_env["AVALON_APP"], prep_env["AVALON_APP_NAME"]] - tools_env = asset_document["data"].get("tools_env") or [] - tools_attr.extend(tools_env) - - tools_env = acre.get_tools(tools_attr) - env = acre.compute(tools_env) - env = acre.merge(env, current_env=dict(prep_env)) - - # Get path to execute - st_temp_path = os.environ["PYPE_CONFIG"] - os_plat = platform.system().lower() - - # Path to folder with launchers - path = os.path.join(st_temp_path, "launchers", os_plat) - - # Full path to executable launcher - execfile = None - - if application.get("launch_hook"): - hook = application.get("launch_hook") - self.log.info("launching hook: {}".format(hook)) - ret_val = pypelib.execute_hook( - application.get("launch_hook"), env=env) - if not ret_val: - return { - 'success': False, - 'message': "Hook didn't finish successfully {0}" - .format(self.label) - } - - if sys.platform == "win32": - for ext in os.environ["PATHEXT"].split(os.pathsep): - fpath = os.path.join(path.strip('"'), self.executable + ext) - if os.path.isfile(fpath) and os.access(fpath, os.X_OK): - execfile = fpath - break - - # Run SW if was found executable - if execfile is None: - return { - "success": False, - "message": "We didn't find launcher for {0}".format( - self.label - ) - } - - popen = avalon.lib.launch( - executable=execfile, args=[], environment=env - ) - - elif (sys.platform.startswith("linux") - or sys.platform.startswith("darwin")): - execfile = os.path.join(path.strip('"'), self.executable) - if not os.path.isfile(execfile): - msg = "Launcher doesn't exist - {}".format(execfile) - - self.log.error(msg) - return { - "success": False, - "message": msg - } - - try: - fp = open(execfile) - except PermissionError as perm_exc: - msg = "Access denied on launcher {} - {}".format( - execfile, perm_exc - ) - - self.log.exception(msg, exc_info=True) - return { - "success": False, - "message": msg - } - - fp.close() - # check executable permission - if not os.access(execfile, os.X_OK): - msg = "No executable permission - {}".format(execfile) - - self.log.error(msg) - return { - "success": False, - "message": msg - } - - # Run SW if was found executable - if execfile is None: - return { - "success": False, - "message": "We didn't found launcher for {0}".format( - self.label - ) - } - - popen = avalon.lib.launch( # noqa: F841 - "/usr/bin/env", args=["bash", execfile], environment=env - ) - # Change status of task to In progress presets = config.get_presets()["ftrack"]["ftrack_config"] From 83ac4df7b68db7f4983db6f37560e34c5b494fa9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 01:48:30 +0200 Subject: [PATCH 394/947] added application action to pype.lib --- pype/lib.py | 32 ++++++++++++++++++++++++++++++++ pype/tools/launcher/lib.py | 5 +++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index ff0c0c0e82..46dd2b781d 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -1590,3 +1590,35 @@ def launch_application(project_name, asset_name, task_name, app_name): "/usr/bin/env", args=["bash", execfile], environment=env ) return popen + + +class ApplicationAction(avalon.api.Action): + """Default application launcher + + This is a convenience application Action that when "config" refers to a + parsed application `.toml` this can launch the application. + + """ + + config = None + required_session_keys = ( + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK" + ) + + def is_compatible(self, session): + for key in self.required_session_keys: + if key not in session: + return False + return True + + def process(self, session, **kwargs): + """Process the full Application action""" + + project_name = session["AVALON_PROJECT"] + asset_name = session["AVALON_ASSET"] + task_name = session["AVALON_TASK"] + return launch_application( + project_name, asset_name, task_name, self.name + ) diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index 0bbbb55560..027ae96e84 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -16,9 +16,10 @@ provides a bridge between the file-based project inventory and configuration. import os from Qt import QtGui -from avalon import lib, pipeline +from avalon import lib from avalon.vendor import qtawesome from pype.api import resources +from pype.lib import ApplicationAction ICON_CACHE = {} NOT_FOUND = type("NotFound", (object, ), {}) @@ -52,7 +53,7 @@ def get_application_actions(project): action = type( "app_{}".format(app_name), - (pipeline.Application,), + (ApplicationAction,), { "name": app_name, "label": label, From 4f2ffa6022af28355e49bc13c5713a2f8490b658 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 02:21:47 +0200 Subject: [PATCH 395/947] final launcher touches --- pype/tools/launcher/__init__.py | 11 +- pype/tools/launcher/__main__.py | 5 - pype/tools/launcher/actions.py | 12 +- pype/tools/launcher/app.py | 242 ++------------------------------ pype/tools/launcher/lib.py | 1 + 5 files changed, 24 insertions(+), 247 deletions(-) delete mode 100644 pype/tools/launcher/__main__.py diff --git a/pype/tools/launcher/__init__.py b/pype/tools/launcher/__init__.py index 3b88ebe984..8f7bf5a769 100644 --- a/pype/tools/launcher/__init__.py +++ b/pype/tools/launcher/__init__.py @@ -1,10 +1,7 @@ - -from .app import ( - show, - cli -) +from .app import LauncherWindow +from . import actions __all__ = [ - "show", - "cli", + "LauncherWindow", + "actions" ] diff --git a/pype/tools/launcher/__main__.py b/pype/tools/launcher/__main__.py deleted file mode 100644 index 50642c46cd..0000000000 --- a/pype/tools/launcher/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -from app import cli - -if __name__ == '__main__': - import sys - sys.exit(cli(sys.argv[1:])) diff --git a/pype/tools/launcher/actions.py b/pype/tools/launcher/actions.py index 44ba9a3a60..80e6f71ae7 100644 --- a/pype/tools/launcher/actions.py +++ b/pype/tools/launcher/actions.py @@ -8,7 +8,6 @@ class ProjectManagerAction(api.Action): name = "projectmanager" label = "Project Manager" icon = "gear" - group = "Test" order = 999 # at the end def is_compatible(self, session): @@ -28,8 +27,7 @@ class LoaderAction(api.Action): name = "loader" label = "Loader" icon = "cloud-download" - order = 998 # at the end - group = "Test" + order = 998 def is_compatible(self, session): return "AVALON_PROJECT" in session @@ -38,7 +36,7 @@ class LoaderAction(api.Action): return lib.launch( executable="python", args=[ - "-u", "-m", "avalon.tools.cbloader", session['AVALON_PROJECT'] + "-u", "-m", "avalon.tools.loader", session['AVALON_PROJECT'] ] ) @@ -72,8 +70,10 @@ def register_config_actions(): module_name = os.environ["AVALON_CONFIG"] config = importlib.import_module(module_name) if not hasattr(config, "register_launcher_actions"): - print("Current configuration `%s` has no 'register_launcher_actions'" - % config.__name__) + print( + "Current configuration `%s` has no 'register_launcher_actions'" + % config.__name__ + ) return config.register_launcher_actions() diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index 6f554110dc..b6a7d4dab2 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -1,10 +1,10 @@ -import sys import copy -from avalon.vendor.Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets, QtCore, QtGui from avalon import style from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from pype.api import resources from avalon.tools import lib as tools_lib from avalon.tools.widgets import AssetWidget @@ -16,10 +16,6 @@ from .widgets import ( from .flickcharm import FlickCharm -module = sys.modules[__name__] -module.window = None - - class IconListView(QtWidgets.QListView): """Styled ListView that allows to toggle between icon and list mode. @@ -244,17 +240,21 @@ class AssetsPanel(QtWidgets.QWidget): return session -class Window(QtWidgets.QDialog): +class LauncherWindow(QtWidgets.QDialog): """Launcher interface""" def __init__(self, parent=None): - super(Window, self).__init__(parent) + super(LauncherWindow, self).__init__(parent) self.dbcon = DbConnector() self.setWindowTitle("Launcher") self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose, False) + + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) + self.setStyleSheet(style.load_stylesheet()) # Allow minimize self.setWindowFlags( @@ -287,8 +287,10 @@ class Window(QtWidgets.QDialog): # Vertically split Pages and Actions body = QtWidgets.QSplitter() body.setContentsMargins(0, 0, 0, 0) - body.setSizePolicy(QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Expanding) + body.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Expanding + ) body.setOrientation(QtCore.Qt.Vertical) body.addWidget(page_slider) body.addWidget(actions_bar) @@ -462,221 +464,3 @@ class Window(QtWidgets.QDialog): # requires a forced refresh first self.asset_panel.on_asset_changed() self.asset_panel.assets_widget.select_task(task_name) - - -class Application(QtWidgets.QApplication): - def __init__(self, *args): - super(Application, self).__init__(*args) - - # Set app icon - icon_path = tools_lib.resource("icons", "png", "avalon-logo-16.png") - icon = QtGui.QIcon(icon_path) - - self.setWindowIcon(icon) - - # Toggles - self.toggles = {"autoHide": False} - - # Timers - keep_visible = QtCore.QTimer(self) - keep_visible.setInterval(100) - keep_visible.setSingleShot(True) - - timers = {"keepVisible": keep_visible} - - tray = QtWidgets.QSystemTrayIcon(icon) - tray.setToolTip("Avalon Launcher") - - # Signals - tray.activated.connect(self.on_tray_activated) - self.aboutToQuit.connect(self.on_quit) - - menu = self.build_menu() - tray.setContextMenu(menu) - tray.show() - - tray.showMessage("Avalon", "Launcher started.") - - # Don't close the app when we close the log window. - # self.setQuitOnLastWindowClosed(False) - - self.focusChanged.connect(self.on_focus_changed) - - window = Window() - window.setAttribute(QtCore.Qt.WA_DeleteOnClose, False) - - self.timers = timers - self._tray = tray - self._window = window - - # geometry = self.calculate_window_geometry(window) - # window.setGeometry(geometry) - - def show(self): - """Show the primary GUI - - This also activates the window and deals with platform-differences. - - """ - - self._window.show() - self._window.raise_() - self._window.activateWindow() - - self.timers["keepVisible"].start() - - def on_tray_activated(self, reason): - if self._window.isVisible(): - self._window.hide() - - elif reason == QtWidgets.QSystemTrayIcon.Trigger: - self.show() - - def on_focus_changed(self, old, new): - """Respond to window losing focus""" - window = new - keep_visible = self.timers["keepVisible"].isActive() - self._window.hide() if (self.toggles["autoHide"] and - not window and - not keep_visible) else None - - def on_autohide_changed(self, auto_hide): - """Respond to changes to auto-hide - - Auto-hide is changed in the UI and determines whether or not - the UI hides upon losing focus. - - """ - - self.toggles["autoHide"] = auto_hide - self.echo("Hiding when losing focus" if auto_hide else "Stays visible") - - def on_quit(self): - """Respond to the application quitting""" - self._tray.hide() - - def build_menu(self): - """Build the right-mouse context menu for the tray icon""" - menu = QtWidgets.QMenu() - - icon = qtawesome.icon("fa.eye", color=style.colors.default) - open = QtWidgets.QAction(icon, "Open", self) - open.triggered.connect(self.show) - - def toggle(): - self.on_autohide_changed(not self.toggles['autoHide']) - - keep_open = QtWidgets.QAction("Keep open", self) - keep_open.setCheckable(True) - keep_open.setChecked(not self.toggles['autoHide']) - keep_open.triggered.connect(toggle) - - quit = QtWidgets.QAction("Quit", self) - quit.triggered.connect(self.quit) - - menu.setStyleSheet(""" - QMenu { - padding: 0px; - margin: 0px; - } - """) - - for action in [open, keep_open, quit]: - menu.addAction(action) - - return menu - - def calculate_window_geometry(self, window): - """Respond to status changes - - On creation, align window with where the tray icon is - located. For example, if the tray icon is in the upper - right corner of the screen, then this is where the - window is supposed to appear. - - Arguments: - status (int): Provided by Qt, the status flag of - loading the input file. - - """ - - tray_x = self._tray.geometry().x() - tray_y = self._tray.geometry().y() - - width = window.width() - width = max(width, window.minimumWidth()) - - height = window.height() - height = max(height, window.sizeHint().height()) - - desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() - screen_geometry = window.geometry() - - screen_width = screen_geometry.width() - screen_height = screen_geometry.height() - - # Calculate width and height of system tray - systray_width = screen_geometry.width() - desktop_geometry.width() - systray_height = screen_geometry.height() - desktop_geometry.height() - - padding = 10 - - x = screen_width - width - y = screen_height - height - - if tray_x < (screen_width / 2): - x = 0 + systray_width + padding - else: - x -= systray_width + padding - - if tray_y < (screen_height / 2): - y = 0 + systray_height + padding - else: - y -= systray_height + padding - - return QtCore.QRect(x, y, width, height) - - -def show(root=None, debug=False, parent=None): - """Display Loader GUI - - Arguments: - debug (bool, optional): Run loader in debug-mode, - defaults to False - parent (QtCore.QObject, optional): When provided parent the interface - to this QObject. - - """ - - app = Application(sys.argv) - app.setStyleSheet(style.load_stylesheet()) - - # Show the window on launch - app.show() - - app.exec_() - - -def cli(args): - import argparse - parser = argparse.ArgumentParser() - #parser.add_argument("project") - - args = parser.parse_args(args) - #project = args.project - - import launcher.actions as actions - print("Registering default actions..") - actions.register_default_actions() - print("Registering config actions..") - actions.register_config_actions() - print("Registering environment actions..") - actions.register_environment_actions() - io.install() - - #io.Session["AVALON_PROJECT"] = project - - import traceback - sys.excepthook = lambda typ, val, tb: traceback.print_last() - - show() diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index 027ae96e84..d307e146d5 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -95,6 +95,7 @@ def get_action_icon(action): ) except Exception: + ICON_CACHE[icon_name] = NOT_FOUND print("Can't load icon \"{}\"".format(icon_name)) return icon From eb16141a0e3c07583dd4efd4fc0c0d113548e725 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 02:22:01 +0200 Subject: [PATCH 396/947] modified avalon_apps module --- pype/modules/avalon_apps/avalon_app.py | 33 ++++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/pype/modules/avalon_apps/avalon_app.py b/pype/modules/avalon_apps/avalon_app.py index d103a84d90..393e1fe755 100644 --- a/pype/modules/avalon_apps/avalon_app.py +++ b/pype/modules/avalon_apps/avalon_app.py @@ -1,10 +1,7 @@ -import os -import argparse -from Qt import QtGui, QtWidgets +from Qt import QtWidgets from avalon.tools import libraryloader from pype.api import Logger -from avalon import io -from launcher import launcher_widget, lib as launcher_lib +from pype.tools.launcher import LauncherWindow, actions class AvalonApps: @@ -12,7 +9,12 @@ class AvalonApps: self.log = Logger().get_logger(__name__) self.main_parent = main_parent self.parent = parent - self.app_launcher = None + + self.app_launcher = LauncherWindow() + + # actions.register_default_actions() + actions.register_config_actions() + actions.register_environment_actions() def process_modules(self, modules): if "RestApiServer" in modules: @@ -32,23 +34,18 @@ class AvalonApps: self.log.warning('Parent menu is not set') return - icon = QtGui.QIcon(launcher_lib.resource("icon", "main.png")) - aShowLauncher = QtWidgets.QAction(icon, "&Launcher", parent_menu) - aLibraryLoader = QtWidgets.QAction("Library", parent_menu) + action_launcher = QtWidgets.QAction("Launcher", parent_menu) + action_library_loader = QtWidgets.QAction("Library", parent_menu) - aShowLauncher.triggered.connect(self.show_launcher) - aLibraryLoader.triggered.connect(self.show_library_loader) + action_launcher.triggered.connect(self.show_launcher) + action_library_loader.triggered.connect(self.show_library_loader) - parent_menu.addAction(aShowLauncher) - parent_menu.addAction(aLibraryLoader) + parent_menu.addAction(action_launcher) + parent_menu.addAction(action_library_loader) def show_launcher(self): # if app_launcher don't exist create it/otherwise only show main window - if self.app_launcher is None: - io.install() - APP_PATH = launcher_lib.resource("qml", "main.qml") - self.app_launcher = launcher_widget.Launcher(APP_PATH) - self.app_launcher.window.show() + self.app_launcher.show() def show_library_loader(self): libraryloader.show( From 03c3b4213ca0f155d4cd32589a1ed4687feada99 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 02:30:28 +0200 Subject: [PATCH 397/947] modified library loader action label --- pype/modules/avalon_apps/avalon_app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/modules/avalon_apps/avalon_app.py b/pype/modules/avalon_apps/avalon_app.py index 393e1fe755..34fbc5c5ae 100644 --- a/pype/modules/avalon_apps/avalon_app.py +++ b/pype/modules/avalon_apps/avalon_app.py @@ -35,7 +35,9 @@ class AvalonApps: return action_launcher = QtWidgets.QAction("Launcher", parent_menu) - action_library_loader = QtWidgets.QAction("Library", parent_menu) + action_library_loader = QtWidgets.QAction( + "Library loader", parent_menu + ) action_launcher.triggered.connect(self.show_launcher) action_library_loader.triggered.connect(self.show_library_loader) From 68df56b31b79535b9f51e05aa776dbeb9478cd01 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 02:33:32 +0200 Subject: [PATCH 398/947] do not use group delegate yet --- pype/tools/launcher/widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 4fc7d166cb..391f9f90f7 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -92,8 +92,9 @@ class ActionBar(QtWidgets.QWidget): model = ActionModel(self.dbcon, self) view.setModel(model) - delegate = ActionDelegate(model.GROUP_ROLE, self) - view.setItemDelegate(delegate) + # TODO better group delegate + # delegate = ActionDelegate(model.GROUP_ROLE, self) + # view.setItemDelegate(delegate) layout.addWidget(view) From 7c6849ce6a649aec763c63a89c05f3ae8218dc00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 02:39:55 +0200 Subject: [PATCH 399/947] small tweaks --- pype/tools/launcher/app.py | 1 + pype/tools/launcher/delegates.py | 4 ++-- pype/tools/launcher/widgets.py | 2 +- pype/tools/tray/__main__.py | 10 +++++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/app.py index b6a7d4dab2..df2a47eed8 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/app.py @@ -16,6 +16,7 @@ from .widgets import ( from .flickcharm import FlickCharm + class IconListView(QtWidgets.QListView): """Styled ListView that allows to toggle between icon and list mode. diff --git a/pype/tools/launcher/delegates.py b/pype/tools/launcher/delegates.py index 750301cec4..8e1ec2004e 100644 --- a/pype/tools/launcher/delegates.py +++ b/pype/tools/launcher/delegates.py @@ -3,8 +3,8 @@ from Qt import QtCore, QtWidgets, QtGui class ActionDelegate(QtWidgets.QStyledItemDelegate): extender_lines = 2 - extender_bg_brush = QtGui.QBrush(QtGui.QColor(100, 100, 100))#, 160)) - extender_fg = QtGui.QColor(255, 255, 255)#, 160) + extender_bg_brush = QtGui.QBrush(QtGui.QColor(100, 100, 100, 160)) + extender_fg = QtGui.QColor(255, 255, 255, 160) def __init__(self, group_role, *args, **kwargs): super(ActionDelegate, self).__init__(*args, **kwargs) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 391f9f90f7..21546e286e 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -2,7 +2,7 @@ import copy from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome -from .delegates import ActionDelegate +# from .delegates import ActionDelegate from .models import TaskModel, ActionModel, ProjectModel from .flickcharm import FlickCharm diff --git a/pype/tools/tray/__main__.py b/pype/tools/tray/__main__.py index d0006c0afe..94d5461dc4 100644 --- a/pype/tools/tray/__main__.py +++ b/pype/tools/tray/__main__.py @@ -1,4 +1,12 @@ +import os import sys import pype_tray -sys.exit(pype_tray.PypeTrayApplication().exec_()) +app = pype_tray.PypeTrayApplication() +if os.name == "nt": + import ctypes + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + u"pype_tray" + ) + +sys.exit(app.exec_()) From 26142007452d7c0d213797af729587da39ef1354 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 03:01:32 +0200 Subject: [PATCH 400/947] app.py renamed to window.py --- pype/tools/launcher/__init__.py | 2 +- pype/tools/launcher/{app.py => window.py} | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) rename pype/tools/launcher/{app.py => window.py} (97%) diff --git a/pype/tools/launcher/__init__.py b/pype/tools/launcher/__init__.py index 8f7bf5a769..109d642e86 100644 --- a/pype/tools/launcher/__init__.py +++ b/pype/tools/launcher/__init__.py @@ -1,4 +1,4 @@ -from .app import LauncherWindow +from .window import LauncherWindow from . import actions __all__ = [ diff --git a/pype/tools/launcher/app.py b/pype/tools/launcher/window.py similarity index 97% rename from pype/tools/launcher/app.py rename to pype/tools/launcher/window.py index df2a47eed8..b53b5b415c 100644 --- a/pype/tools/launcher/app.py +++ b/pype/tools/launcher/window.py @@ -1,4 +1,5 @@ import copy +import logging from Qt import QtWidgets, QtCore, QtGui from avalon import style @@ -247,6 +248,9 @@ class LauncherWindow(QtWidgets.QDialog): def __init__(self, parent=None): super(LauncherWindow, self).__init__(parent) + self.log = logging.getLogger( + ".".join([__name__, self.__class__.__name__]) + ) self.dbcon = DbConnector() self.setWindowTitle("Launcher") @@ -365,7 +369,7 @@ class LauncherWindow(QtWidgets.QDialog): def echo(self, message): self.message_label.setText(str(message)) QtCore.QTimer.singleShot(5000, lambda: self.message_label.setText("")) - print(message) + self.log.debug(message) def on_project_changed(self): project_name = self.asset_panel.project_bar.get_current_project() @@ -399,7 +403,7 @@ class LauncherWindow(QtWidgets.QDialog): self.actions_bar.model.refresh() def on_action_clicked(self, action): - self.echo("Running action: %s" % action.name) + self.echo("Running action: {}".format(action.name)) self.run_action(action) def on_history_action(self, history_data): @@ -440,7 +444,11 @@ class LauncherWindow(QtWidgets.QDialog): self.action_history.add_action(action, session) # Process the Action - action().process(session) + try: + action().process(session) + except Exception as exc: + self.log.warning("Action launch failed.", exc_info=True) + self.echo("Failed: {}".format(str(exc))) def set_session(self, session): project_name = session.get("AVALON_PROJECT") From f011223f22ec987ab84db6a582a21775989c598e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 15 Aug 2020 03:05:14 +0200 Subject: [PATCH 401/947] hound fix --- pype/tools/launcher/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index 61e240c2eb..fee09e4f4b 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -76,9 +76,9 @@ class TaskModel(QtGui.QStandardItemModel): if not asset_docs: return - task_names = collections.Counter() + task_names = set() for asset_doc in asset_docs: - asset_tasks = asset_doc.get("data", {}).get("tasks", []) + asset_tasks = asset_doc.get("data", {}).get("tasks") or set() task_names.update(asset_tasks) self.beginResetModel() @@ -89,7 +89,7 @@ class TaskModel(QtGui.QStandardItemModel): self.appendRow(item) else: - for task_name, count in sorted(task_names.items()): + for task_name in sorted(task_names): icon = self._icons.get(task_name, self.default_icon) item = QtGui.QStandardItem(icon, task_name) self.appendRow(item) From 0b48d447295384c1abc7a5744561fe1cc79ce4f1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 17 Aug 2020 08:54:35 +0100 Subject: [PATCH 402/947] Make keepImages a class attribute --- pype/plugins/maya/create/create_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/maya/create/create_review.py b/pype/plugins/maya/create/create_review.py index c488a7559c..d76b78c580 100644 --- a/pype/plugins/maya/create/create_review.py +++ b/pype/plugins/maya/create/create_review.py @@ -11,6 +11,7 @@ class CreateReview(avalon.maya.Creator): family = "review" icon = "video-camera" defaults = ['Main'] + keepImages = False def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) @@ -21,6 +22,6 @@ class CreateReview(avalon.maya.Creator): for key, value in animation_data.items(): data[key] = value - data["keepImages"] = False + data["keepImages"] = self.keepImages self.data = data From 3a096e5b638ccf47199eb30d7c390f6a5c06d432 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 10:10:18 +0200 Subject: [PATCH 403/947] fixed actions history --- pype/tools/launcher/widgets.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 21546e286e..c3a908c9dd 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -3,6 +3,7 @@ from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome # from .delegates import ActionDelegate +from . import lib from .models import TaskModel, ActionModel, ProjectModel from .flickcharm import FlickCharm @@ -264,9 +265,7 @@ class ActionHistory(QtWidgets.QPushButton): m = "{{action:{0}}} | {{breadcrumb}}".format(largest_action_label) label = m.format(action=action.label, breadcrumb=breadcrumb) - icon_name = action.icon - color = action.color or "white" - icon = qtawesome.icon("fa.%s" % icon_name, color=color) + icon = lib.get_action_icon(action) item = QtWidgets.QListWidgetItem(icon, label) item.setData(action_session_role, (action, session)) From 00523f0c500412721c50907ef35deacc3c7c7569 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:02:23 +0200 Subject: [PATCH 404/947] added label_variant to actions --- pype/tools/launcher/lib.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index d307e146d5..25270fcbfe 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -48,15 +48,16 @@ def get_application_actions(project): icon = app_definition.get("icon", app.get("icon", "folder-o")) color = app_definition.get("color", app.get("color", None)) order = app_definition.get("order", app.get("order", 0)) - label = app.get("label") or app_definition.get("label") or app["name"] - group = app.get("group") or app_definition.get("group") - + label = app_definition.get("label") or app.get("label") or app_name + label_variant = app_definition.get("label_variant") + group = app_definition.get("group") or app.get("group") action = type( "app_{}".format(app_name), (ApplicationAction,), { "name": app_name, "label": label, + "label_variant": label_variant, "group": group, "icon": icon, "color": color, From 15f97fbad1ef10657be83f00d2efea7996bce567 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:02:56 +0200 Subject: [PATCH 405/947] variantss are showing in proper way --- pype/tools/launcher/lib.py | 11 +++++++ pype/tools/launcher/models.py | 54 +++++++++++++++++++++++++++------- pype/tools/launcher/widgets.py | 51 ++++++++++++++++++++++++++++---- 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/pype/tools/launcher/lib.py b/pype/tools/launcher/lib.py index 25270fcbfe..a6d6ff6865 100644 --- a/pype/tools/launcher/lib.py +++ b/pype/tools/launcher/lib.py @@ -100,3 +100,14 @@ def get_action_icon(action): print("Can't load icon \"{}\"".format(icon_name)) return icon + + +def get_action_label(action): + label = getattr(action, "label", None) + if not label: + return action.name + + label_variant = getattr(action, "label_variant", None) + if not label_variant: + return label + return " ".join([label, label_variant]) diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index fee09e4f4b..f76e26afde 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -155,25 +155,57 @@ class ActionModel(QtGui.QStandardItemModel): self.beginResetModel() single_actions = [] + varianted_actions = collections.defaultdict(list) grouped_actions = collections.defaultdict(list) for action in actions: + # Groups group_name = getattr(action, "group", None) - if not group_name: - single_actions.append(action) - else: + + # Lable variants + label = getattr(action, "label", None) + label_variant = getattr(action, "label_variant", None) + if label_variant and not label: + print(( + "Invalid action \"{}\" has set `label_variant` to \"{}\"" + ", but doesn't have set `label` attribute" + ).format(action.name, label_variant)) + action.label_variant = None + label_variant = None + + if group_name: grouped_actions[group_name].append(action) - for group_name, actions in tuple(grouped_actions.items()): - if len(actions) == 1: - grouped_actions.pop(group_name) - single_actions.append(actions[0]) + elif label_variant: + varianted_actions[label].append(action) + else: + single_actions.append(action) items_by_order = collections.defaultdict(list) + for label, actions in tuple(varianted_actions.items()): + if len(actions) == 1: + varianted_actions.pop(label) + single_actions.append(actions[0]) + continue + + icon = None + order = None + for action in actions: + if icon is None: + _icon = lib.get_action_icon(action) + if _icon: + icon = _icon + + if order is None or action.order < order: + order = action.order + + item = QtGui.QStandardItem(icon, action.label) + item.setData(actions, self.ACTION_ROLE) + item.setData(True, self.GROUP_ROLE) + items_by_order[order].append(item) + for action in single_actions: icon = self.get_icon(action) - item = QtGui.QStandardItem( - icon, str(action.label or action.name) - ) + item = QtGui.QStandardItem(icon, lib.get_action_label(action)) item.setData(action, self.ACTION_ROLE) items_by_order[action.order].append(item) @@ -185,7 +217,7 @@ class ActionModel(QtGui.QStandardItemModel): order = action.order if icon is None: - _icon = self.get_icon(action) + _icon = lib.get_action_icon(action) if _icon: icon = _icon diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index c3a908c9dd..774c3de5ee 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -1,4 +1,5 @@ import copy +import collections from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome @@ -123,13 +124,53 @@ class ActionBar(QtWidgets.QWidget): self.action_clicked.emit(action) return - menu = QtWidgets.QMenu(self) actions = index.data(self.model.ACTION_ROLE) - actions_mapping = {} + by_variant_label = collections.defaultdict(list) + orders = [] for action in actions: - menu_action = QtWidgets.QAction(action.label or action.name) - menu.addAction(menu_action) - actions_mapping[menu_action] = action + # Lable variants + label = getattr(action, "label", None) + label_variant = getattr(action, "label_variant", None) + if label_variant and not label: + label_variant = None + + if not label_variant: + orders.append(action) + continue + + if label not in orders: + orders.append(label) + by_variant_label[label].append(action) + + menu = QtWidgets.QMenu(self) + actions_mapping = {} + + for action_item in orders: + actions = by_variant_label.get(action_item) + if not actions: + action = action_item + elif len(actions) == 1: + action = actions[0] + else: + action = None + + if action: + menu_action = QtWidgets.QAction( + lib.get_action_label(action) + ) + menu.addAction(menu_action) + actions_mapping[menu_action] = action + continue + + sub_menu = QtWidgets.QMenu(label, menu) + for action in actions: + menu_action = QtWidgets.QAction( + lib.get_action_label(action) + ) + sub_menu.addAction(menu_action) + actions_mapping[menu_action] = action + + menu.addMenu(sub_menu) result = menu.exec_(QtGui.QCursor.pos()) if result: From 8b4f209da42fe4e20d8c5cccfec4b4ba5c09f7bd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:03:19 +0200 Subject: [PATCH 406/947] added group and variant to ApplicationAction --- pype/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/lib.py b/pype/lib.py index 46dd2b781d..00bd72a164 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -1601,6 +1601,8 @@ class ApplicationAction(avalon.api.Action): """ config = None + group = None + variant = None required_session_keys = ( "AVALON_PROJECT", "AVALON_ASSET", From 0bda8c0d2faf86d0b5c47abb1f407491979b312e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:03:40 +0200 Subject: [PATCH 407/947] use non perfect group delegate to make clear what's grouped --- pype/tools/launcher/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 774c3de5ee..82435e8681 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -3,7 +3,7 @@ import collections from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome -# from .delegates import ActionDelegate +from .delegates import ActionDelegate from . import lib from .models import TaskModel, ActionModel, ProjectModel from .flickcharm import FlickCharm @@ -95,8 +95,8 @@ class ActionBar(QtWidgets.QWidget): view.setModel(model) # TODO better group delegate - # delegate = ActionDelegate(model.GROUP_ROLE, self) - # view.setItemDelegate(delegate) + delegate = ActionDelegate(model.GROUP_ROLE, self) + view.setItemDelegate(delegate) layout.addWidget(view) From 33ea814c890a9c3c71d1f18945786f3ab7856e4e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:23:59 +0200 Subject: [PATCH 408/947] variants are working now --- pype/tools/launcher/delegates.py | 10 ++-- pype/tools/launcher/models.py | 3 +- pype/tools/launcher/widgets.py | 87 ++++++++++++++++++-------------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/pype/tools/launcher/delegates.py b/pype/tools/launcher/delegates.py index 8e1ec2004e..95ccde6445 100644 --- a/pype/tools/launcher/delegates.py +++ b/pype/tools/launcher/delegates.py @@ -6,13 +6,17 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate): extender_bg_brush = QtGui.QBrush(QtGui.QColor(100, 100, 100, 160)) extender_fg = QtGui.QColor(255, 255, 255, 160) - def __init__(self, group_role, *args, **kwargs): + def __init__(self, group_roles, *args, **kwargs): super(ActionDelegate, self).__init__(*args, **kwargs) - self.group_role = group_role + self.group_roles = group_roles def paint(self, painter, option, index): super(ActionDelegate, self).paint(painter, option, index) - is_group = index.data(self.group_role) + is_group = False + for group_role in self.group_roles: + is_group = index.data(group_role) + if is_group: + break if not is_group: return diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index f76e26afde..3fb201702e 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -109,6 +109,7 @@ class TaskModel(QtGui.QStandardItemModel): class ActionModel(QtGui.QStandardItemModel): ACTION_ROLE = QtCore.Qt.UserRole GROUP_ROLE = QtCore.Qt.UserRole + 1 + VARIANT_GROUP_ROLE = QtCore.Qt.UserRole + 2 def __init__(self, dbcon, parent=None): super(ActionModel, self).__init__(parent=parent) @@ -200,7 +201,7 @@ class ActionModel(QtGui.QStandardItemModel): item = QtGui.QStandardItem(icon, action.label) item.setData(actions, self.ACTION_ROLE) - item.setData(True, self.GROUP_ROLE) + item.setData(True, self.VARIANT_GROUP_ROLE) items_by_order[order].append(item) for action in single_actions: diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 82435e8681..7ab0a3f8ea 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -95,7 +95,10 @@ class ActionBar(QtWidgets.QWidget): view.setModel(model) # TODO better group delegate - delegate = ActionDelegate(model.GROUP_ROLE, self) + delegate = ActionDelegate( + [model.GROUP_ROLE, model.VARIANT_GROUP_ROLE], + self + ) view.setItemDelegate(delegate) layout.addWidget(view) @@ -119,58 +122,68 @@ class ActionBar(QtWidgets.QWidget): return is_group = index.data(self.model.GROUP_ROLE) - if not is_group: + is_variant_group = index.data(self.model.VARIANT_GROUP_ROLE) + if not is_group and not is_variant_group: action = index.data(self.model.ACTION_ROLE) self.action_clicked.emit(action) return actions = index.data(self.model.ACTION_ROLE) - by_variant_label = collections.defaultdict(list) - orders = [] - for action in actions: - # Lable variants - label = getattr(action, "label", None) - label_variant = getattr(action, "label_variant", None) - if label_variant and not label: - label_variant = None - - if not label_variant: - orders.append(action) - continue - - if label not in orders: - orders.append(label) - by_variant_label[label].append(action) menu = QtWidgets.QMenu(self) actions_mapping = {} - for action_item in orders: - actions = by_variant_label.get(action_item) - if not actions: - action = action_item - elif len(actions) == 1: - action = actions[0] - else: - action = None - - if action: + if is_variant_group: + for action in actions: menu_action = QtWidgets.QAction( lib.get_action_label(action) ) menu.addAction(menu_action) actions_mapping[menu_action] = action - continue - - sub_menu = QtWidgets.QMenu(label, menu) + else: + by_variant_label = collections.defaultdict(list) + orders = [] for action in actions: - menu_action = QtWidgets.QAction( - lib.get_action_label(action) - ) - sub_menu.addAction(menu_action) - actions_mapping[menu_action] = action + # Lable variants + label = getattr(action, "label", None) + label_variant = getattr(action, "label_variant", None) + if label_variant and not label: + label_variant = None - menu.addMenu(sub_menu) + if not label_variant: + orders.append(action) + continue + + if label not in orders: + orders.append(label) + by_variant_label[label].append(action) + + for action_item in orders: + actions = by_variant_label.get(action_item) + if not actions: + action = action_item + elif len(actions) == 1: + action = actions[0] + else: + action = None + + if action: + menu_action = QtWidgets.QAction( + lib.get_action_label(action) + ) + menu.addAction(menu_action) + actions_mapping[menu_action] = action + continue + + sub_menu = QtWidgets.QMenu(label, menu) + for action in actions: + menu_action = QtWidgets.QAction( + lib.get_action_label(action) + ) + sub_menu.addAction(menu_action) + actions_mapping[menu_action] = action + + menu.addMenu(sub_menu) result = menu.exec_(QtGui.QCursor.pos()) if result: From 3b18458cac54e7c99b5e484f1f9508b767085ccb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:39:22 +0200 Subject: [PATCH 409/947] fixed action history --- pype/tools/launcher/window.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/pype/tools/launcher/window.py b/pype/tools/launcher/window.py index b53b5b415c..70a8b0fc2e 100644 --- a/pype/tools/launcher/window.py +++ b/pype/tools/launcher/window.py @@ -317,18 +317,6 @@ class LauncherWindow(QtWidgets.QDialog): self.asset_panel = asset_panel self.actions_bar = actions_bar self.action_history = action_history - - self.data = { - "pages": { - "project": project_panel, - "asset": asset_panel - }, - "model": { - "actions": actions_bar, - "action_history": action_history - }, - } - self.page_slider = page_slider self._page = 0 @@ -458,10 +446,14 @@ class LauncherWindow(QtWidgets.QDialog): if project_name: # Force the "in project" view. - self.pages.slide_view(1, direction="right") - index = self.asset_panel.project_bar.view.findText(project_name) + self.page_slider.slide_view(1, direction="right") + index = self.asset_panel.project_bar.project_combobox.findText( + project_name + ) if index >= 0: - self.asset_panel.project_bar.view.setCurrentIndex(index) + self.asset_panel.project_bar.project_combobox.setCurrentIndex( + index + ) if silo: self.asset_panel.assets_widget.set_silo(silo) @@ -472,4 +464,4 @@ class LauncherWindow(QtWidgets.QDialog): if task_name: # requires a forced refresh first self.asset_panel.on_asset_changed() - self.asset_panel.assets_widget.select_task(task_name) + self.asset_panel.tasks_widget.select_task(task_name) From b2cbfe98d934be40f219377000feb3d30b583ed3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 17 Aug 2020 12:42:05 +0200 Subject: [PATCH 410/947] reverse logic of action history --- pype/tools/launcher/window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/launcher/window.py b/pype/tools/launcher/window.py index 70a8b0fc2e..13b4abee6e 100644 --- a/pype/tools/launcher/window.py +++ b/pype/tools/launcher/window.py @@ -401,11 +401,11 @@ class LauncherWindow(QtWidgets.QDialog): is_control_down = QtCore.Qt.ControlModifier & modifiers if is_control_down: - # User is holding control, rerun the action - self.run_action(action, session=session) - else: # Revert to that "session" location self.set_session(session) + else: + # User is holding control, rerun the action + self.run_action(action, session=session) def get_current_session(self): if self._page == 1: From f52fe3beefcfba1bd0ef744d943aa044a4b83e96 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 17 Aug 2020 15:31:44 +0100 Subject: [PATCH 411/947] Add "isolate" to reviewable subset --- pype/plugins/maya/publish/collect_review.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 063a854bd1..e16b6ae9f9 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -63,6 +63,7 @@ class CollectReview(pyblish.api.InstancePlugin): data['handles'] = instance.data.get('handles', None) data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] + data["isolate"] = instance.data["isolate"] cmds.setAttr(str(instance) + '.active', 1) self.log.debug('data {}'.format(instance.context[i].data)) instance.context[i].data.update(data) From 71bfa3774a944a65201bf779bc5335d61b6c57ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 17 Aug 2020 16:50:29 +0200 Subject: [PATCH 412/947] submit each assembly job separately --- .../global/publish/submit_publish_job.py | 5 ++-- .../maya/publish/submit_maya_deadline.py | 30 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index d106175eb6..1a3d0df1b3 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -897,8 +897,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # # Batch name reflect original scene name - if instance.data.get("assemblySubmissionJob"): - render_job["Props"]["Batch"] = instance.data.get("jobBatchName") + if instance.data.get("assemblySubmissionJobs"): + render_job["Props"]["Batch"] = instance.data.get( + "jobBatchName") else: render_job["Props"]["Batch"] = os.path.splitext( os.path.basename(context.data.get("currentFile")))[0] diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index d6d4bd2910..afa5496455 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -638,6 +638,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "Path is unreachable: `{}`".format( os.path.dirname(config_file))) + # add config file as job auxFile + assembly_payloads[hash]["AuxFiles"] = [config_file] + with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) print("ImageFileName={}".format(file), file=cf) @@ -658,20 +661,23 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): for k, v in tiles.items(): print("{}={}".format(k, v), file=cf) - self.log.debug(json.dumps(assembly_payloads, - indent=4, sort_keys=True)) - self.log.info( - "Submitting assembly job(s) [{}] ...".format(len(assembly_payloads))) # noqa: E501 - url = "{}/api/jobs".format(self._deadline_url) - response = self._requests_post(url, json={ - "Jobs": list(assembly_payloads.values()), - "AuxFiles": [] - }) - if not response.ok: - raise Exception(response) + job_idx = 1 + instance.data["assemblySubmissionJobs"] = [] + for k, ass_job in assembly_payloads.items(): + self.log.info("submitting assembly job {} of {}".format( + job_idx, len(assembly_payloads) + )) + self.log.debug(json.dumps(ass_job, indent=4, sort_keys=True)) + response = self._requests_post(url, json=ass_job) + if not response.ok: + raise Exception(response.text) + + instance.data["assemblySubmissionJobs"].append(ass_job) + job_idx += 1 - instance.data["assemblySubmissionJob"] = assembly_payloads instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] + self.log.info("Setting batch name on instance: {}".format( + instance.data["jobBatchName"])) else: # Submit job to farm -------------------------------------------- self.log.info("Submitting ...") From 9d917f5fa42a9075089bb5154e8a2c6a6a102d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 17 Aug 2020 17:20:36 +0200 Subject: [PATCH 413/947] delete unused tile validator --- .../validate_deadline_tile_submission.py | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 pype/plugins/maya/publish/validate_deadline_tile_submission.py diff --git a/pype/plugins/maya/publish/validate_deadline_tile_submission.py b/pype/plugins/maya/publish/validate_deadline_tile_submission.py deleted file mode 100644 index b0b995de3e..0000000000 --- a/pype/plugins/maya/publish/validate_deadline_tile_submission.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -"""Validate settings from Deadline Submitter. - -This is useful mainly for tile rendering, where jobs on farm are created by -submitter script from Maya. - -Unfortunately Deadline doesn't expose frame number for tiles job so that -cannot be validated, even if it is important setting. Also we cannot -determine if 'Region Rendering' (tile rendering) is enabled or not because -of the same thing. - -""" -import os - -from maya import mel -from maya import cmds - -import pyblish.api -from pype.hosts.maya import lib - - -class ValidateDeadlineTileSubmission(pyblish.api.InstancePlugin): - """Validate Deadline Submission settings are OK for tile rendering.""" - - label = "Validate Deadline Tile Submission" - order = pyblish.api.ValidatorOrder - hosts = ["maya"] - families = ["renderlayer"] - if not os.environ.get("DEADLINE_REST_URL"): - active = False - - def process(self, instance): - """Entry point.""" - # try if Deadline submitter was loaded - if mel.eval("exists SubmitJobToDeadline") == 0: - # if not, try to load it manually - try: - mel.eval("source DeadlineMayaClient;") - except RuntimeError: - raise AssertionError("Deadline Maya client cannot be loaded") - mel.eval("DeadlineMayaClient();") - assert mel.eval("exists SubmitJobToDeadline") == 1, ( - "Deadline Submission script cannot be initialized.") - if instance.data.get("tileRendering"): - job_name = cmds.getAttr("defaultRenderGlobals.deadlineJobName") - scene_name = os.path.splitext(os.path.basename( - instance.context.data.get("currentFile")))[0] - if job_name != scene_name: - self.log.warning(("Job submitted through Deadline submitter " - "has different name then current scene " - "{} / {}").format(job_name, scene_name)) - if cmds.getAttr("defaultRenderGlobals.deadlineTileSingleJob") == 1: - layer = instance.data['setMembers'] - anim_override = lib.get_attr_in_layer( - "defaultRenderGlobals.animation", layer=layer) - assert anim_override, ( - "Animation must be enabled in " - "Render Settings even when rendering single frame." - ) - - start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") - end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") - assert start_frame == end_frame, ( - "Start frame and end frame are not equals. When " - "'Submit All Tles As A Single Job' is selected, only " - "single frame is expected to be rendered. It must match " - "the one specified in Deadline Submitter under " - "'Region Rendering'" - ) From 1f55e22c1313cfc5122186b22b90d5d3befd651e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 17 Aug 2020 17:24:15 +0200 Subject: [PATCH 414/947] fix hound warning --- pype/plugins/maya/publish/submit_maya_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index afa5496455..cf17de3445 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -663,7 +663,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): job_idx = 1 instance.data["assemblySubmissionJobs"] = [] - for k, ass_job in assembly_payloads.items(): + for _k, ass_job in assembly_payloads.items(): self.log.info("submitting assembly job {} of {}".format( job_idx, len(assembly_payloads) )) From c65ab39d330d58009b73c2fecadeb4ce442e8853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Mon, 17 Aug 2020 21:46:48 +0200 Subject: [PATCH 415/947] fix prefix path for tiles --- pype/plugins/maya/publish/submit_maya_deadline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index cf17de3445..34e4432aa5 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -130,8 +130,8 @@ def _format_tiles(filename, index, tiles_x, tiles_y, width, height, prefix): os.path.basename(filename) ) out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = tile_prefix.join( # noqa: E501 - prefix.rsplit("/", 1)) + out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 @@ -252,7 +252,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): optional = True use_published = True - tile_assembler_plugin = "DraftTileAssembler" + tile_assembler_plugin = "PypeTileAssembler" def process(self, instance): """Plugin entry point.""" From 5e552598bf291d8b8da295b3b110cccaaff20732 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 17 Aug 2020 22:04:54 +0100 Subject: [PATCH 416/947] Copy bit rate of input video to match quality. --- pype/scripts/otio_burnin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 16e24757dd..718943855c 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -524,6 +524,10 @@ def burnins_from_data( profile_name = profile_name.replace(" ", "_").lower() ffmpeg_args.append("-profile:v {}".format(profile_name)) + bit_rate = burnin._streams[0].get("bit_rate") + if bit_rate: + ffmpeg_args.append("--b:v {}".format(bit_rate)) + pix_fmt = burnin._streams[0].get("pix_fmt") if pix_fmt: ffmpeg_args.append("-pix_fmt {}".format(pix_fmt)) From 63ae53a8d2ebd0e4da081a5a13e9a1e4590cce9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 18 Aug 2020 14:49:30 +0200 Subject: [PATCH 417/947] fix job naming for multiple frames --- pype/plugins/maya/publish/submit_maya_deadline.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 34e4432aa5..ca796d0a1c 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -566,13 +566,16 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): file_index = 1 for file in files: frame = re.search(R_FRAME_NUMBER, file).group("frame") - new_payload = copy.copy(payload) + new_payload = copy.deepcopy(payload) new_payload["JobInfo"]["Name"] = \ "{} (Frame {} - {} tiles)".format( - new_payload["JobInfo"]["Name"], + payload["JobInfo"]["Name"], frame, instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 ) + self.log.info( + "... preparing job {}".format( + new_payload["JobInfo"]["Name"])) new_payload["JobInfo"]["TileJobFrame"] = frame tiles_data = _format_tiles( @@ -592,7 +595,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): frame_payloads.append(new_payload) - new_assembly_payload = copy.copy(assembly_payload) + new_assembly_payload = copy.deepcopy(assembly_payload) new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( REPL_FRAME_NUMBER, "\\1{}\\3".format("#" * len(frame)), file) From 42a19156f49fdfb184c6b2686b7feb24c387317a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 18 Aug 2020 15:30:32 +0200 Subject: [PATCH 418/947] fix assembly job names --- pype/plugins/maya/publish/submit_maya_deadline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index ca796d0a1c..eeb6472850 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -596,6 +596,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): frame_payloads.append(new_payload) new_assembly_payload = copy.deepcopy(assembly_payload) + new_assembly_payload["JobInfo"]["Name"] = \ + "{} (Frame {})".format( + assembly_payload["JobInfo"]["Name"], + frame) new_assembly_payload["JobInfo"]["OutputFilename0"] = re.sub( REPL_FRAME_NUMBER, "\\1{}\\3".format("#" * len(frame)), file) From 866bc150085f3eeb82f6042f9866f130de95b400 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 Aug 2020 15:53:38 +0200 Subject: [PATCH 419/947] reraise launcher window if is already shown but in back --- pype/modules/avalon_apps/avalon_app.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/modules/avalon_apps/avalon_app.py b/pype/modules/avalon_apps/avalon_app.py index 34fbc5c5ae..7ed651f82b 100644 --- a/pype/modules/avalon_apps/avalon_app.py +++ b/pype/modules/avalon_apps/avalon_app.py @@ -48,6 +48,8 @@ class AvalonApps: def show_launcher(self): # if app_launcher don't exist create it/otherwise only show main window self.app_launcher.show() + self.app_launcher.raise_() + self.app_launcher.activateWindow() def show_library_loader(self): libraryloader.show( From bd6717fe20d889919ad6d7e67050cbae5b6d8ca0 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 18 Aug 2020 15:56:09 +0200 Subject: [PATCH 420/947] fix shell popping up with ffmpeg --- pype/plugins/global/publish/extract_burnin.py | 4 +- pype/plugins/global/publish/extract_jpeg.py | 2 +- pype/plugins/global/publish/extract_review.py | 10 +- .../global/publish/extract_review_slate.py | 4 +- .../publish/extract_review.py | 199 ------------------ .../publish/extract_thumbnail.py | 122 ----------- 6 files changed, 13 insertions(+), 328 deletions(-) delete mode 100644 pype/plugins/standalonepublisher/publish/extract_review.py delete mode 100644 pype/plugins/standalonepublisher/publish/extract_thumbnail.py diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index e1508b9131..dedfd98979 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -224,7 +224,7 @@ class ExtractBurnin(pype.api.Extractor): self.log.debug("Executing: {}".format(args)) # Run burnin script - output = pype.api.subprocess(args) + output = pype.api.subprocess(args, shell=True) self.log.debug("Output: {}".format(output)) for filepath in temp_data["full_input_paths"]: @@ -964,7 +964,7 @@ class ExtractBurnin(pype.api.Extractor): args = [executable, scriptpath, json_data] self.log.debug("Executing: {}".format(args)) - output = pype.api.subprocess(args) + output = pype.api.subprocess(args, shell=True) self.log.debug("Output: {}".format(output)) repre_update = { diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index 9b775f8b6f..97af9a3751 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -83,7 +83,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): # run subprocess self.log.debug("{}".format(subprocess_jpeg)) - pype.api.subprocess(subprocess_jpeg) + pype.api.subprocess(subprocess_jpeg, shell=True) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index e1a0d7043a..ac8766f5a7 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -175,7 +175,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - output = pype.api.subprocess(subprcs_cmd) + output = pype.api.subprocess(subprcs_cmd, shell=True) self.log.debug("Output: {}".format(output)) output_name = output_def["filename_suffix"] @@ -342,6 +342,12 @@ class ExtractReview(pyblish.api.InstancePlugin): "-i \"{}\"".format(temp_data["full_input_path"]) ) + if temp_data["output_is_sequence"]: + # Set start frame + ffmpeg_input_args.append( + "-start_number {}".format(temp_data["output_frame_start"]) + ) + # Add audio arguments if there are any. Skipped when output are images. if not temp_data["output_ext_is_image"]: audio_in_args, audio_filters, audio_out_args = self.audio_args( @@ -1482,7 +1488,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - output = pype.api.subprocess(subprcs_cmd) + output = pype.api.subprocess(subprcs_cmd, shell=True) self.log.debug("Output: {}".format(output)) # create representation data diff --git a/pype/plugins/global/publish/extract_review_slate.py b/pype/plugins/global/publish/extract_review_slate.py index f2ea6c0875..2e1fc25ae5 100644 --- a/pype/plugins/global/publish/extract_review_slate.py +++ b/pype/plugins/global/publish/extract_review_slate.py @@ -186,7 +186,7 @@ class ExtractReviewSlate(pype.api.Extractor): # run slate generation subprocess self.log.debug("Slate Executing: {}".format(slate_subprcs_cmd)) - slate_output = pype.api.subprocess(slate_subprcs_cmd) + slate_output = pype.api.subprocess(slate_subprcs_cmd, shell=True) self.log.debug("Slate Output: {}".format(slate_output)) # create ffmpeg concat text file path @@ -221,7 +221,7 @@ class ExtractReviewSlate(pype.api.Extractor): # ffmpeg concat subprocess self.log.debug("Executing concat: {}".format(concat_subprcs_cmd)) - concat_output = pype.api.subprocess(concat_subprcs_cmd) + concat_output = pype.api.subprocess(concat_subprcs_cmd, shell=True) self.log.debug("Output concat: {}".format(concat_output)) self.log.debug("__ repre[tags]: {}".format(repre["tags"])) diff --git a/pype/plugins/standalonepublisher/publish/extract_review.py b/pype/plugins/standalonepublisher/publish/extract_review.py deleted file mode 100644 index 0f845afcb1..0000000000 --- a/pype/plugins/standalonepublisher/publish/extract_review.py +++ /dev/null @@ -1,199 +0,0 @@ -import os -import tempfile - -import pyblish.api -import clique -import pype.api -import pype.lib - - -class ExtractReviewSP(pyblish.api.InstancePlugin): - """Extracting Review mov file for Ftrack - - Compulsory attribute of representation is tags list with "review", - otherwise the representation is ignored. - - All new represetnations are created and encoded by ffmpeg following - presets found in `pype-config/presets/plugins/global/publish.json:ExtractReview:outputs`. To change the file extension - filter values use preset's attributes `ext_filter` - """ - - label = "Extract Review SP" - order = pyblish.api.ExtractorOrder + 0.02 - families = ["review"] - hosts = ["standalonepublisher"] - - def process(self, instance): - # adding plugin attributes from presets - presets = instance.context.data["presets"] - try: - publish_presets = presets["plugins"]["standalonepublisher"]["publish"] - plugin_attrs = publish_presets[self.__class__.__name__] - except KeyError: - raise KeyError("Preset for plugin \"{}\" are not set".format( - self.__class__.__name__ - )) - - output_profiles = plugin_attrs.get("outputs", {}) - - fps = instance.data.get("fps") - start_frame = instance.data.get("frameStart") - - self.log.debug("Families In: `{}`".format(instance.data["families"])) - - # get specific profile if was defined - specific_profiles = instance.data.get("repreProfiles", []) - - new_repres = [] - # filter out mov and img sequences - for repre in instance.data["representations"]: - tags = repre.get("tags", []) - if "review" not in tags: - continue - - staging_dir = repre["stagingDir"] - for name in specific_profiles: - profile = output_profiles.get(name) - if not profile: - self.log.warning( - "Profile \"{}\" was not found in presets".format(name) - ) - continue - - self.log.debug("Processing profile: {}".format(name)) - - ext = profile.get("ext", None) - if not ext: - ext = "mov" - self.log.debug(( - "`ext` attribute not in output profile \"{}\"." - " Setting to default ext: `mov`" - ).format(name)) - - if isinstance(repre["files"], list): - collections, remainder = clique.assemble(repre["files"]) - - full_input_path = os.path.join( - staging_dir, - collections[0].format("{head}{padding}{tail}") - ) - filename = collections[0].format('{head}') - if filename.endswith("."): - filename = filename[:-1] - else: - full_input_path = os.path.join(staging_dir, repre["files"]) - filename = repre["files"].split(".")[0] - - # prepare output file - repr_file = filename + "_{0}.{1}".format(name, ext) - out_stagigng_dir = tempfile.mkdtemp(prefix="extract_review_") - full_output_path = os.path.join(out_stagigng_dir, repr_file) - - self.log.info("input {}".format(full_input_path)) - self.log.info("output {}".format(full_output_path)) - - repre_new = repre.copy() - - new_tags = [x for x in tags if x != "delete"] - p_tags = profile.get("tags", []) - self.log.info("p_tags: `{}`".format(p_tags)) - - for _tag in p_tags: - if _tag not in new_tags: - new_tags.append(_tag) - - self.log.info("new_tags: `{}`".format(new_tags)) - - input_args = [] - - # overrides output file - input_args.append("-y") - - # preset's input data - input_args.extend(profile.get("input", [])) - - # necessary input data - # adds start arg only if image sequence - if isinstance(repre["files"], list): - input_args.extend([ - "-start_number {}".format(start_frame), - "-framerate {}".format(fps) - ]) - - input_args.append("-i {}".format(full_input_path)) - - output_args = [] - # preset's output data - output_args.extend(profile.get("output", [])) - - if isinstance(repre["files"], list): - # set length of video by len of inserted files - video_len = len(repre["files"]) - else: - video_len = repre["frameEnd"] - repre["frameStart"] + 1 - output_args.append( - "-frames {}".format(video_len) - ) - - # letter_box - lb_string = ( - "-filter:v " - "drawbox=0:0:iw:round((ih-(iw*(1/{0})))/2):t=fill:c=black," - "drawbox=0:ih-round((ih-(iw*(1/{0})))/2):iw:" - "round((ih-(iw*(1/{0})))/2):t=fill:c=black" - ) - letter_box = profile.get("letter_box", None) - if letter_box: - output_args.append(lb_string.format(letter_box)) - - # output filename - output_args.append(full_output_path) - - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") - mov_args = [ - ffmpeg_path, - " ".join(input_args), - " ".join(output_args) - ] - subprcs_cmd = " ".join(mov_args) - - # run subprocess - self.log.debug("Executing: {}".format(subprcs_cmd)) - output = pype.api.subprocess(subprcs_cmd) - self.log.debug("Output: {}".format(output)) - - # create representation data - repre_new.update({ - "name": name, - "ext": ext, - "files": repr_file, - "stagingDir": out_stagigng_dir, - "tags": new_tags, - "outputName": name, - "frameStartFtrack": 1, - "frameEndFtrack": video_len - }) - # cleanup thumbnail from new repre - if repre_new.get("thumbnail"): - repre_new.pop("thumbnail") - if "thumbnail" in repre_new["tags"]: - repre_new["tags"].remove("thumbnail") - - # adding representation - self.log.debug("Adding: {}".format(repre_new)) - # cleanup repre from preview - if "preview" in repre: - repre.pop("preview") - if "preview" in repre["tags"]: - repre["tags"].remove("preview") - new_repres.append(repre_new) - - for repre in instance.data["representations"]: - if "delete" in repre.get("tags", []): - instance.data["representations"].remove(repre) - - for repre in new_repres: - self.log.debug("Adding repre: \"{}\"".format( - repre - )) - instance.data["representations"].append(repre) diff --git a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py deleted file mode 100644 index cddc9c3a82..0000000000 --- a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py +++ /dev/null @@ -1,122 +0,0 @@ -import os -import tempfile -import subprocess -import pyblish.api -import pype.api -import pype.lib - - -class ExtractThumbnailSP(pyblish.api.InstancePlugin): - """Extract jpeg thumbnail from component input from standalone publisher - - Uses jpeg file from component if possible (when single or multiple jpegs - are loaded to component selected as thumbnail) otherwise extracts from - input file/s single jpeg to temp. - """ - - label = "Extract Thumbnail SP" - hosts = ["standalonepublisher"] - order = pyblish.api.ExtractorOrder - - # Presetable attribute - ffmpeg_args = None - - def process(self, instance): - repres = instance.data.get('representations') - if not repres: - return - - thumbnail_repre = None - for repre in repres: - if repre.get("thumbnail"): - thumbnail_repre = repre - break - - if not thumbnail_repre: - return - - files = thumbnail_repre.get("files") - if not files: - return - - if isinstance(files, list): - files_len = len(files) - file = str(files[0]) - else: - files_len = 1 - file = files - - is_jpeg = False - if file.endswith(".jpeg") or file.endswith(".jpg"): - is_jpeg = True - - if is_jpeg and files_len == 1: - # skip if already is single jpeg file - return - - elif is_jpeg: - # use first frame as thumbnail if is sequence of jpegs - full_thumbnail_path = file - self.log.info( - "For thumbnail is used file: {}".format(full_thumbnail_path) - ) - - else: - # Convert to jpeg if not yet - full_input_path = os.path.join(thumbnail_repre["stagingDir"], file) - self.log.info("input {}".format(full_input_path)) - - full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] - self.log.info("output {}".format(full_thumbnail_path)) - - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") - - ffmpeg_args = self.ffmpeg_args or {} - - jpeg_items = [] - jpeg_items.append(ffmpeg_path) - # override file if already exists - jpeg_items.append("-y") - # add input filters from peresets - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format(full_input_path)) - # extract only single file - jpeg_items.append("-vframes 1") - - jpeg_items.extend(ffmpeg_args.get("output") or []) - - # output file - jpeg_items.append(full_thumbnail_path) - - subprocess_jpeg = " ".join(jpeg_items) - - # run subprocess - self.log.debug("Executing: {}".format(subprocess_jpeg)) - subprocess.Popen( - subprocess_jpeg, - stdout=subprocess.PIPE, - shell=True - ) - - # remove thumbnail key from origin repre - thumbnail_repre.pop("thumbnail") - - filename = os.path.basename(full_thumbnail_path) - staging_dir = os.path.dirname(full_thumbnail_path) - - # create new thumbnail representation - representation = { - 'name': 'jpg', - 'ext': 'jpg', - 'files': filename, - "stagingDir": staging_dir, - "thumbnail": True, - "tags": [] - } - - # # add Delete tag when temp file was rendered - # if not is_jpeg: - # representation["tags"].append("delete") - - instance.data["representations"].append(representation) From 8f9fb484ce48d7a8ac967a711648d9a3cf7adab2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 18 Aug 2020 17:33:58 +0200 Subject: [PATCH 421/947] added missing argument to launch_application in ftrack app handler --- pype/modules/ftrack/lib/ftrack_app_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 4847464973..23776aced7 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -149,7 +149,9 @@ class AppAction(BaseAction): asset_name = entity["parent"]["name"] project_name = entity["project"]["full_name"] try: - pypelib.launch_application(project_name, asset_name, task_name) + pypelib.launch_application( + project_name, asset_name, task_name, self.identifier + ) except pypelib.ApplicationLaunchFailed as exc: self.log.error(str(exc)) From 7e65e5c830a63cf05aff5438cbcd1f18bfd57ec2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 Aug 2020 12:06:59 +0200 Subject: [PATCH 422/947] basic overrides values getting implemented --- pype/tools/config_setting/widgets/base.py | 61 +++++++++++- pype/tools/config_setting/widgets/inputs.py | 101 +++++++++++++++++--- 2 files changed, 150 insertions(+), 12 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 14ff56aa2a..c131966f6c 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -304,6 +304,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.is_overidable = False self.ignore_value_changes = False + self.project_name = None self.input_fields = [] @@ -373,7 +374,6 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): def _on_project_change(self): project_name = self.project_list_widget.project_name() - if project_name is None: overrides = None self.is_overidable = False @@ -381,12 +381,28 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): overrides = config.project_preset_overrides(project_name) self.is_overidable = True + self.project_name = project_name self.ignore_value_changes = True for item in self.input_fields: item.apply_overrides(overrides) self.ignore_value_changes = False def _save(self): + if self.project_name is None: + self._save_defaults() + else: + self._save_overrides() + + def _save_overrides(self): + output = {} + for item in self.input_fields: + value = item.overrides() + if value is not NOT_SET: + output.update(value) + + print(json.dumps(output, indent=4)) + + def _save_defaults(self): output = {} for item in self.input_fields: output.update(item.config_value()) @@ -396,3 +412,46 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): output = _output print(json.dumps(output, indent=4)) + return + + # TODO check implementation copied from studio + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["studio"] + + # Load studio data with metadata + current_presets = config.studio_presets() + + keys_to_file = config.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + origin_values = current_presets + for key in key_sequence: + if key not in origin_values: + origin_values = {} + break + origin_values = origin_values[key] + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + origin_values.update(new_values) + + output_path = os.path.join( + config.studio_presets_path, subpath + ) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(output_path, "w") as file_stream: + json.dump(origin_values, file_stream, indent=4) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index c4ec7a4347..6c6c31408e 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -177,6 +177,11 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) @@ -321,6 +326,11 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) @@ -473,6 +483,11 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) @@ -617,6 +632,11 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) @@ -757,6 +777,11 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class RawJsonInput(QtWidgets.QPlainTextEdit): tab_length = 4 @@ -955,6 +980,11 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 @@ -1248,6 +1278,11 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overriden: + return NOT_SET + return self.config_value() + class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) @@ -1473,6 +1508,18 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.input_fields.append(item) return item + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET + + values = {} + for input_field in self.input_fields: + value = input_field.overrides() + if value is NOT_SET: + continue + values.update(value) + return {self.key: values} + class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): # TODO is not overridable by itself @@ -1612,6 +1659,18 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): ) self.update_style() + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET + + values = {} + for input_field in self.input_fields: + value = input_field.overrides() + if value is NOT_SET: + continue + values.update(value) + return {self.key: values} + class DictFormWidget(QtWidgets.QWidget): value_changed = QtCore.Signal(object) @@ -1628,7 +1687,7 @@ class DictFormWidget(QtWidgets.QWidget): self.any_parent_is_group = any_parent_is_group self.is_modified = False - self.is_overriden = False + self._is_overriden = False self.is_group = False super(DictFormWidget, self).__init__(parent) @@ -1646,13 +1705,9 @@ class DictFormWidget(QtWidgets.QWidget): return self.value_changed.emit(self) - def item_value(self): - output = {} - for input_field in self.input_fields.values(): - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output + @property + def is_overriden(self): + return self._parent.is_overriden @property def child_modified(self): @@ -1676,9 +1731,6 @@ class DictFormWidget(QtWidgets.QWidget): def ignore_value_changes(self): return self._parent.ignore_value_changes - def config_value(self): - return self.item_value() - def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] key = child_configuration["key"] @@ -1697,6 +1749,28 @@ class DictFormWidget(QtWidgets.QWidget): self.input_fields[key] = item return item + def item_value(self): + output = {} + for input_field in self.input_fields.values(): + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return self.item_value() + + def overrides(self): + if not self.is_overiden and not self.child_overriden: + return NOT_SET + + values = {} + for input_field in self.input_fields: + value = input_field.overrides() + if value is not NOT_SET: + values.update(value) + return values + class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 @@ -2064,6 +2138,11 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + def overrides(self): + if not self.is_overiden: + return NOT_SET + return self.config_value() + TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["text-singleline"] = TextSingleLineWidget From 76992b0629c6e99a0fb68e2084516c933bcd6dbe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 Aug 2020 15:13:41 +0200 Subject: [PATCH 423/947] overrides are collected with metadata info --- pype/tools/config_setting/widgets/__init__.py | 2 +- pype/tools/config_setting/widgets/base.py | 17 +- pype/tools/config_setting/widgets/inputs.py | 1050 ++++++++--------- pype/tools/config_setting/widgets/lib.py | 11 +- 4 files changed, 537 insertions(+), 543 deletions(-) diff --git a/pype/tools/config_setting/widgets/__init__.py b/pype/tools/config_setting/widgets/__init__.py index b295759a36..9fbce6e1cf 100644 --- a/pype/tools/config_setting/widgets/__init__.py +++ b/pype/tools/config_setting/widgets/__init__.py @@ -1,4 +1,4 @@ -from .lib import CustomNone, NOT_SET +from .lib import NOT_SET, AS_WIDGET, METADATA_KEY from .base import * diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index c131966f6c..156f1f80e4 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -1,12 +1,10 @@ import os import json -import copy from Qt import QtWidgets, QtCore, QtGui from . import config from .widgets import UnsavedChangesDialog -from .lib import NOT_SET +from .lib import NOT_SET, METADATA_KEY from avalon import io -from queue import Queue class TypeToKlass: @@ -394,12 +392,19 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._save_overrides() def _save_overrides(self): - output = {} + data = {} + groups = [] for item in self.input_fields: - value = item.overrides() + value, is_group = item.overrides() if value is not NOT_SET: - output.update(value) + data.update(value) + if is_group: + groups.extend(value.keys()) + + if groups: + data[METADATA_KEY] = {"groups": groups} + output = convert_to_override(data) print(json.dumps(output, indent=4)) def _save_defaults(self): diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index 6c6c31408e..bb2d76fc71 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -8,7 +8,7 @@ from .widgets import ( ModifiedIntSpinBox, ModifiedFloatSpinBox ) -from .lib import NOT_SET, AS_WIDGET +from .lib import NOT_SET, AS_WIDGET, METADATA_KEY class SchemeGroupHierarchyBug(Exception): @@ -19,7 +19,14 @@ class SchemeGroupHierarchyBug(Exception): super(SchemeGroupHierarchyBug, self).__init__(msg) -class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class InputWidget: + def overrides(self): + if not self.is_overriden: + return NOT_SET, False + return self.config_value(), self.is_group + + +class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -177,13 +184,8 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - -class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -326,13 +328,8 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - -class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -483,13 +480,8 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - -class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -632,13 +624,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - -class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -777,11 +764,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - class RawJsonInput(QtWidgets.QPlainTextEdit): tab_length = 4 @@ -840,7 +822,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.update_style(is_valid) -class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -980,11 +962,6 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 @@ -1146,7 +1123,7 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: self.item_value()} -class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -1278,499 +1255,6 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - def overrides(self): - if not self.is_overriden: - return NOT_SET - return self.config_value() - - -class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - if values is AS_WIDGET: - raise TypeError("Can't use \"{}\" as widget item.".format( - self.__class__.__name__ - )) - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - self.any_parent_is_group = any_parent_is_group - - self._is_modified = False - self._is_overriden = False - self.is_group = is_group - - self._state = None - self._child_state = None - - super(DictExpandWidget, self).__init__(parent) - self.setObjectName("DictExpandWidget") - top_part = ClickableWidget(parent=self) - - button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) - button_toggle.setProperty("btn-type", "expand-toggle") - button_toggle.setIconSize(button_size) - button_toggle.setArrowType(QtCore.Qt.RightArrow) - button_toggle.setCheckable(True) - button_toggle.setChecked(False) - - label = input_data["label"] - button_toggle_text = QtWidgets.QLabel(label, parent=top_part) - button_toggle_text.setObjectName("ExpandLabel") - - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(button_toggle_text) - top_part.setLayout(layout) - - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 9) - - content_widget = QtWidgets.QWidget(self) - content_widget.setVisible(False) - - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) - - main_layout.addWidget(top_part) - main_layout.addWidget(content_widget) - self.setLayout(main_layout) - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - self.top_part = top_part - self.button_toggle = button_toggle - self.button_toggle_text = button_toggle_text - - self.content_widget = content_widget - self.content_layout = content_layout - - self.top_part.clicked.connect(self._top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) - - self.input_fields = [] - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def _top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) - - def toggle_content(self, *args): - if len(args) > 0: - checked = args[0] - else: - checked = self.button_toggle.isChecked() - arrow_type = QtCore.Qt.RightArrow - if checked: - arrow_type = QtCore.Qt.DownArrow - self.button_toggle.setChecked(checked) - self.button_toggle.setArrowType(arrow_type) - self.content_widget.setVisible(checked) - self.parent().updateGeometry() - - def resizeEvent(self, event): - super(DictExpandWidget, self).resizeEvent(event) - self.content_widget.updateGeometry() - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def apply_overrides(self, override_value): - # Make sure this is set to False - self._is_overriden = False - self._state = None - self._child_state = None - for item in self.input_fields: - if override_value is None: - child_value = None - else: - child_value = override_value.get(item.key) - - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden - ) - ) - self.update_style() - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - - # TODO update items - if item is not None: - for _item in self.input_fields: - if _item is not item: - _item.update_style() - - self.value_changed.emit(self) - - self.update_style() - - def update_style(self, is_overriden=None): - child_modified = self.child_modified - child_state = self.style_state(self.child_overriden, child_modified) - if child_state: - child_state = "child-{}".format(child_state) - - if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) - self._child_state = child_state - - state = self.style_state(self.is_overriden, self.is_modified) - if self._state == state: - return - - self.button_toggle_text.setProperty("state", state) - self.button_toggle_text.style().polish(self.button_toggle_text) - - self._state = state - - @property - def is_modified(self): - if self.is_group: - return self.child_modified - return False - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return {self.key: self.item_value()} - - @property - def is_overidable(self): - return self._parent.is_overidable - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - item = klass( - child_configuration, values, self.keys, self - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addWidget(item) - - self.input_fields.append(item) - return item - - def overrides(self): - if not self.is_overriden and not self.child_overriden: - return NOT_SET - - values = {} - for input_field in self.input_fields: - value = input_field.overrides() - if value is NOT_SET: - continue - values.update(value) - return {self.key: values} - - -class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): - # TODO is not overridable by itself - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - self.any_parent_is_group = any_parent_is_group - - self._is_overriden = False - self.is_modified = False - self.is_group = is_group - - super(DictInvisible, self).__init__(parent) - self.setObjectName("DictInvisible") - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - self.input_fields = [] - - if "key" not in input_data: - print(json.dumps(input_data, indent=4)) - - self.key = input_data["key"] - self.keys = list(parent_keys) - self.keys.append(self.key) - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def update_style(self, *args, **kwargs): - return - - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return {self.key: self.item_value()} - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - if item_type == "schema": - for _schema in child_configuration["children"]: - children = config.gui_schema(_schema) - self.add_children_gui(children, values) - return - - klass = TypeToKlass.types.get(item_type) - item = klass( - child_configuration, values, self.keys, self - ) - self.layout().addWidget(item) - - item.value_changed.connect(self._on_value_change) - - self.input_fields.append(item) - return item - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - # TODO update items - if item is not None: - is_overriden = self.is_overriden - for _item in self.input_fields: - if _item is not item: - _item.update_style(is_overriden) - - self.value_changed.emit(self) - - def apply_overrides(self, override_value): - self._is_overriden = False - for item in self.input_fields: - if override_value is None: - child_value = None - else: - child_value = override_value.get(item.key) - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden - ) - ) - self.update_style() - - def overrides(self): - if not self.is_overriden and not self.child_overriden: - return NOT_SET - - values = {} - for input_field in self.input_fields: - value = input_field.overrides() - if value is NOT_SET: - continue - values.update(value) - return {self.key: values} - - -class DictFormWidget(QtWidgets.QWidget): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self.any_parent_is_group = any_parent_is_group - - self.is_modified = False - self._is_overriden = False - self.is_group = False - - super(DictFormWidget, self).__init__(parent) - - self.input_fields = {} - self.content_layout = QtWidgets.QFormLayout(self) - - self.keys = list(parent_keys) - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - self.value_changed.emit(self) - - @property - def is_overriden(self): - return self._parent.is_overriden - - @property - def child_modified(self): - for input_field in self.input_fields.values(): - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields.values(): - if input_field.child_overriden: - return True - return False - - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - key = child_configuration["key"] - # Pop label to not be set in child - label = child_configuration["label"] - - klass = TypeToKlass.types.get(item_type) - - label_widget = QtWidgets.QLabel(label) - - item = klass( - child_configuration, values, self.keys, self, label_widget - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addRow(label_widget, item) - self.input_fields[key] = item - return item - - def item_value(self): - output = {} - for input_field in self.input_fields.values(): - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def config_value(self): - return self.item_value() - - def overrides(self): - if not self.is_overiden and not self.child_overriden: - return NOT_SET - - values = {} - for input_field in self.input_fields: - value = input_field.overrides() - if value is not NOT_SET: - values.update(value) - return values - class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 @@ -2008,7 +1492,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): return output -class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): +class ModifiableDict(ExpandingWidget, PypeConfigurationWidget, InputWidget): # Should be used only for dictionary with one datatype as value # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) @@ -2138,10 +1622,506 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} + +class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + if values is AS_WIDGET: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self._is_modified = False + self._is_overriden = False + self.is_group = is_group + + self._state = None + self._child_state = None + + super(DictExpandWidget, self).__init__(parent) + self.setObjectName("DictExpandWidget") + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label = input_data["label"] + button_toggle_text = QtWidgets.QLabel(label, parent=top_part) + button_toggle_text.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(button_toggle_text) + top_part.setLayout(layout) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget = QtWidgets.QWidget(self) + content_widget.setVisible(False) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + + main_layout.addWidget(top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.button_toggle_text = button_toggle_text + + self.content_widget = content_widget + self.content_layout = content_layout + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + self.input_fields = [] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def resizeEvent(self, event): + super(DictExpandWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + # Make sure this is set to False + self._is_overriden = False + self._state = None + self._child_state = None + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + # TODO update items + if item is not None: + for _item in self.input_fields: + if _item is not item: + _item.update_style() + + self.value_changed.emit(self) + + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_state = self.style_state(self.child_overriden, child_modified) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + self.button_toggle_text.setProperty("state", state) + self.button_toggle_text.style().polish(self.button_toggle_text) + + self._state = state + + @property + def is_modified(self): + if self.is_group: + return self.child_modified + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + @property + def is_overidable(self): + return self._parent.is_overidable + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + def overrides(self): - if not self.is_overiden: - return NOT_SET - return self.config_value() + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(values.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + +class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + # TODO is not overridable by itself + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self._is_overriden = False + self.is_modified = False + self.is_group = is_group + + super(DictInvisible, self).__init__(parent) + self.setObjectName("DictInvisible") + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.input_fields = [] + + if "key" not in input_data: + print(json.dumps(input_data, indent=4)) + + self.key = input_data["key"] + self.keys = list(parent_keys) + self.keys.append(self.key) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def update_style(self, *args, **kwargs): + return + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + if item_type == "schema": + for _schema in child_configuration["children"]: + children = config.gui_schema(_schema) + self.add_children_gui(children, values) + return + + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.layout().addWidget(item) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + # TODO update items + if item is not None: + is_overriden = self.is_overriden + for _item in self.input_fields: + if _item is not item: + _item.update_style(is_overriden) + + self.value_changed.emit(self) + + def apply_overrides(self, override_value): + self._is_overriden = False + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(values.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + +class DictFormWidget(QtWidgets.QWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + + self.is_modified = False + self._is_overriden = False + self.is_group = False + + super(DictFormWidget, self).__init__(parent) + + self.input_fields = {} + self.content_layout = QtWidgets.QFormLayout(self) + + self.keys = list(parent_keys) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self.value_changed.emit(self) + + @property + def is_overriden(self): + return self._parent.is_overriden + + @property + def child_modified(self): + for input_field in self.input_fields.values(): + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields.values(): + if input_field.child_overriden: + return True + return False + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + key = child_configuration["key"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = QtWidgets.QLabel(label) + + item = klass( + child_configuration, values, self.keys, self, label_widget + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields[key] = item + return item + + def item_value(self): + output = {} + for input_field in self.input_fields.values(): + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return self.item_value() + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(values.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group TypeToKlass.types["boolean"] = BooleanWidget diff --git a/pype/tools/config_setting/widgets/lib.py b/pype/tools/config_setting/widgets/lib.py index ac0a353d53..bf6d2d0fbd 100644 --- a/pype/tools/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/widgets/lib.py @@ -41,4 +41,13 @@ class CustomNone: NOT_SET = CustomNone() -AS_WIDGET = CustomNone() +AS_WIDGET = type("AS_WIDGET", (), {}) +METADATA_KEY = type("METADATA_KEY", (), {}) + + +def convert_gui_data_to_overrides(data): + pass + + +def convert_overrides_to_gui_data(data): + pass From a200be829b7beb35b21fbe9c10b5466ae0a9750e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 19 Aug 2020 16:10:41 +0200 Subject: [PATCH 424/947] tile support for separate AOVs and better dep handling --- .../global/publish/submit_publish_job.py | 26 ++++---------- .../maya/publish/submit_maya_deadline.py | 35 ++++++++++++++----- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 1a3d0df1b3..d2f38c328b 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -275,26 +275,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Mandatory for Deadline, may be empty "AuxFiles": [], } - """ - In this part we will add file dependencies instead of job dependencies. - This way we don't need to take care of tile assembly job, getting its - id or name. We expect it to produce specific file with specific name - and we are just waiting for them. - """ + + # add assembly jobs as dependencies if instance.data.get("tileRendering"): - self.log.info("Adding tile assembly results as dependencies...") - asset_index = 0 - for inst in instances: - for represenation in inst.get("representations", []): - if isinstance(represenation["files"], (list, tuple)): - for file in represenation["files"]: - dependency = os.path.join(output_dir, file) - payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 - else: - dependency = os.path.join( - output_dir, represenation["files"]) - payload["JobInfo"]["AssetDependency{}".format(asset_index)] = dependency # noqa: E501 - asset_index += 1 + self.log.info("Adding tile assembly jobs as dependencies...") + job_index = 0 + for assembly_id in instance.data.get("assemblySubmissionJobs"): + payload["JobInfo"]["JobDependency{}".format(job_index)] = assembly_id # noqa: E501 + job_index += 1 else: payload["JobInfo"]["JobDependency0"] = job["_id"] diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index eeb6472850..5baea9d82b 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -24,6 +24,7 @@ import copy import re import hashlib from datetime import datetime +import itertools import clique import requests @@ -548,7 +549,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): assembly_payload["JobInfo"].update(output_filenames) frame_payloads = [] - assembly_payloads = {} + assembly_payloads = [] R_FRAME_NUMBER = re.compile(r".+\.(?P[0-9]+)\..+") # noqa: N806, E501 REPL_FRAME_NUMBER = re.compile(r"(.+\.)([0-9]+)(\..+)") # noqa: N806, E501 @@ -557,11 +558,19 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # we have aovs and we need to iterate over them # get files from `beauty` files = exp[0].get("beauty") + # assembly files are used for assembly jobs as we need to put + # together all AOVs + assembly_files = list( + itertools.chain.from_iterable( + [f for _, f in exp[0].items()])) if not files: # if beauty doesn't exists, use first aov we found files = exp[0].get(list(exp[0].keys())[0]) else: files = exp + assembly_files = files + + frame_jobs = {} file_index = 1 for file in files: @@ -590,11 +599,16 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): new_payload["PluginInfo"].update(tiles_data["PluginInfo"]) job_hash = hashlib.sha256("{}_{}".format(file_index, file)) + frame_jobs[frame] = job_hash.hexdigest() new_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() new_payload["JobInfo"]["ExtraInfo1"] = file frame_payloads.append(new_payload) + file_index += 1 + file_index = 1 + for file in assembly_files: + frame = re.search(R_FRAME_NUMBER, file).group("frame") new_assembly_payload = copy.deepcopy(assembly_payload) new_assembly_payload["JobInfo"]["Name"] = \ "{} (Frame {})".format( @@ -604,9 +618,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): REPL_FRAME_NUMBER, "\\1{}\\3".format("#" * len(frame)), file) - new_assembly_payload["JobInfo"]["ExtraInfo0"] = job_hash.hexdigest() # noqa: E501 + new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[frame] # noqa: E501 new_assembly_payload["JobInfo"]["ExtraInfo1"] = file - assembly_payloads[job_hash.hexdigest()] = new_assembly_payload + assembly_payloads.append(new_assembly_payload) file_index += 1 self.log.info( @@ -622,9 +636,13 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): job_id = response.json()["_id"] hash = response.json()["Props"]["Ex0"] - file = response.json()["Props"]["Ex1"] - assembly_payloads[hash]["JobInfo"]["JobDependency0"] = job_id + for assembly_job in assembly_payloads: + if assembly_job["JobInfo"]["ExtraInfo0"] == hash: + assembly_job["JobInfo"]["JobDependency0"] = job_id + + for assembly_job in assembly_payloads: + file = assembly_job["JobInfo"]["ExtraInfo1"] # write assembly job config files now = datetime.now() @@ -646,7 +664,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): os.path.dirname(config_file))) # add config file as job auxFile - assembly_payloads[hash]["AuxFiles"] = [config_file] + assembly_job["AuxFiles"] = [config_file] with open(config_file, "w") as cf: print("TileCount={}".format(tiles_count), file=cf) @@ -670,7 +688,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): job_idx = 1 instance.data["assemblySubmissionJobs"] = [] - for _k, ass_job in assembly_payloads.items(): + for ass_job in assembly_payloads: self.log.info("submitting assembly job {} of {}".format( job_idx, len(assembly_payloads) )) @@ -679,7 +697,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if not response.ok: raise Exception(response.text) - instance.data["assemblySubmissionJobs"].append(ass_job) + instance.data["assemblySubmissionJobs"].append( + response.json()["_id"]) job_idx += 1 instance.data["jobBatchName"] = payload["JobInfo"]["BatchName"] From a2c8e8a088b8964ed9e9c5873388ca462dcaef94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 19 Aug 2020 16:31:41 +0200 Subject: [PATCH 425/947] assembly job priority --- pype/plugins/maya/publish/submit_maya_deadline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 5baea9d82b..d9ee7f9646 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -547,6 +547,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): } } assembly_payload["JobInfo"].update(output_filenames) + assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( + "priority", 50) frame_payloads = [] assembly_payloads = [] From f1d5ef05aae4a94a3cbf1877537a7b97b091b03a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 Aug 2020 16:47:25 +0200 Subject: [PATCH 426/947] added conversion from override gui data to regular override and back --- pype/tools/config_setting/widgets/base.py | 4 +- pype/tools/config_setting/widgets/config.py | 11 +-- pype/tools/config_setting/widgets/inputs.py | 6 +- pype/tools/config_setting/widgets/lib.py | 80 +++++++++++---------- 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 156f1f80e4..bdacb60559 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -3,7 +3,7 @@ import json from Qt import QtWidgets, QtCore, QtGui from . import config from .widgets import UnsavedChangesDialog -from .lib import NOT_SET, METADATA_KEY +from .lib import NOT_SET, METADATA_KEY, convert_gui_data_to_overrides from avalon import io @@ -404,7 +404,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): if groups: data[METADATA_KEY] = {"groups": groups} - output = convert_to_override(data) + output = convert_gui_data_to_overrides(data) print(json.dumps(output, indent=4)) def _save_defaults(self): diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 8319c3d51d..62a3adb782 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -23,8 +23,9 @@ project_presets_path = os.path.normpath( ) first_run = False -OVERRIDE_KEY = "__overriden__" -POP_KEY = "__popkey__" +OVERRIDEN_KEY = "__overriden_keys__" +# TODO key popping not implemented yet +POP_KEY = "__pop_key__" def load_json(fpath): @@ -178,8 +179,8 @@ def project_preset_overrides(project_name, **kwargs): def merge_overrides(global_dict, override_dict): - if OVERRIDE_KEY in override_dict: - _override = override_dict.pop(OVERRIDE_KEY) + if OVERRIDEN_KEY in override_dict: + _override = override_dict.pop(OVERRIDEN_KEY) if _override: return override_dict @@ -187,7 +188,7 @@ def merge_overrides(global_dict, override_dict): if value == POP_KEY: global_dict.pop(key) - elif key == OVERRIDE_KEY: + elif key == OVERRIDEN_KEY: continue elif key not in global_dict: diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py index bb2d76fc71..1840572cfb 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/widgets/inputs.py @@ -1858,7 +1858,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: values.update(value) if is_group: - groups.extend(values.keys()) + groups.extend(value.keys()) if groups: values[METADATA_KEY] = {"groups": groups} return {self.key: values}, self.is_group @@ -2013,7 +2013,7 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): if value is not NOT_SET: values.update(value) if is_group: - groups.extend(values.keys()) + groups.extend(value.keys()) if groups: values[METADATA_KEY] = {"groups": groups} return {self.key: values}, self.is_group @@ -2118,7 +2118,7 @@ class DictFormWidget(QtWidgets.QWidget): if value is not NOT_SET: values.update(value) if is_group: - groups.extend(values.keys()) + groups.extend(value.keys()) if groups: values[METADATA_KEY] = {"groups": groups} return {self.key: values}, self.is_group diff --git a/pype/tools/config_setting/widgets/lib.py b/pype/tools/config_setting/widgets/lib.py index bf6d2d0fbd..fd3f45b590 100644 --- a/pype/tools/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/widgets/lib.py @@ -1,53 +1,55 @@ -import uuid +from .config import OVERRIDEN_KEY class CustomNone: - """Created object can be used as custom None (not equal to None). - - WARNING: Multiple created objects are not equal either. - Exmple: - >>> a = CustomNone() - >>> a == None - False - >>> b = CustomNone() - >>> a == b - False - >>> a == a - True - """ - - def __init__(self): - """Create uuid as identifier for custom None.""" - self.identifier = str(uuid.uuid4()) - + """Created object can be used as custom None (not equal to None).""" def __bool__(self): """Return False (like default None).""" return False - def __eq__(self, other): - """Equality is compared by identifier value.""" - if type(other) == type(self): - if other.identifier == self.identifier: - return True - return False - - def __str__(self): - """Return value of identifier when converted to string.""" - return "".format(str(self.identifier)) - - def __repr__(self): - """Representation of custom None.""" - return "".format(str(self.identifier)) - NOT_SET = CustomNone() AS_WIDGET = type("AS_WIDGET", (), {}) + METADATA_KEY = type("METADATA_KEY", (), {}) - -def convert_gui_data_to_overrides(data): - pass +OVERRIDE_VERSION = 1 -def convert_overrides_to_gui_data(data): - pass +def convert_gui_data_to_overrides(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if first: + output["__override_version__"] = OVERRIDE_VERSION + + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key == "groups": + print("**", value) + output[OVERRIDEN_KEY] = value + else: + KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_to_overrides(value, False) + return output + + +def convert_overrides_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if OVERRIDEN_KEY in data: + groups = data.pop(OVERRIDEN_KEY) + if METADATA_KEY not in output: + output[METADATA_KEY] = {} + output[METADATA_KEY]["groups"] = groups + + for key, value in data.items(): + output[key] = convert_overrides_to_gui_data(value, False) + + return output From 4ef3d225e8711329c7c03b2b282ad6c57b11ece4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 09:57:34 +0200 Subject: [PATCH 427/947] disable editing of items in tasks and actions --- pype/tools/launcher/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/launcher/widgets.py b/pype/tools/launcher/widgets.py index 7ab0a3f8ea..894dde3926 100644 --- a/pype/tools/launcher/widgets.py +++ b/pype/tools/launcher/widgets.py @@ -85,6 +85,7 @@ class ActionBar(QtWidgets.QWidget): view.setViewMode(QtWidgets.QListView.IconMode) view.setResizeMode(QtWidgets.QListView.Adjust) view.setSelectionMode(QtWidgets.QListView.NoSelection) + view.setEditTriggers(QtWidgets.QListView.NoEditTriggers) view.setWrapping(True) view.setGridSize(QtCore.QSize(70, 75)) view.setIconSize(QtCore.QSize(30, 30)) @@ -206,6 +207,7 @@ class TasksWidget(QtWidgets.QWidget): view = QtWidgets.QTreeView(self) view.setIndentation(0) + view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) model = TaskModel(self.dbcon) view.setModel(model) From eb74ea9253a42e33e1f034bec2c1d83829e39834 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 10:03:59 +0200 Subject: [PATCH 428/947] get rid of metadata loading --- pype/tools/config_setting/widgets/config.py | 36 +++------------------ 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 62a3adb782..8d9d4fa1d2 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -86,31 +86,16 @@ def load_json(fpath): return {} -def subkey_merge(_dict, value, keys, with_metadata=False): +def subkey_merge(_dict, value, keys): key = keys.pop(0) if not keys: - if with_metadata: - _dict[key] = {"type": "file", "value": value} - else: - _dict[key] = value + _dict[key] = value return _dict if key not in _dict: - if with_metadata: - _dict[key] = {"type": "folder", "value": {}} - else: - _dict[key] = {} + _dict[key] = {} + _dict[key] = subkey_merge(_dict[key], value, keys) - if with_metadata: - sub_dict = _dict[key]["value"] - else: - sub_dict = _dict[key] - - _value = subkey_merge(sub_dict, value, keys, with_metadata) - if with_metadata: - _dict[key]["value"] = _value - else: - _dict[key] = _value return _dict @@ -122,7 +107,6 @@ def load_jsons_from_dir(path, *args, **kwargs): # TODO warning return output - with_metadata = kwargs.get("with_metadata") sub_keys = list(kwargs.pop("subkeys", args)) for sub_key in tuple(sub_keys): _path = os.path.join(path, sub_key) @@ -143,7 +127,7 @@ def load_jsons_from_dir(path, *args, **kwargs): # dict_path = os.path.join(base[base_len:], basename) # dict_keys = dict_path.split(os.path.sep) dict_keys = base[base_len:].split(os.path.sep) + [basename] - output = subkey_merge(output, value, dict_keys, with_metadata) + output = subkey_merge(output, value, dict_keys) for sub_key in sub_keys: output = output[sub_key] @@ -158,16 +142,6 @@ def global_project_presets(**kwargs): return load_jsons_from_dir(project_presets_path, **kwargs) -def studio_presets_with_metadata(*args, **kwargs): - kwargs["with_metadata"] = True - return load_jsons_from_dir(studio_presets_path, *args, **kwargs) - - -def global_project_presets_with_metadata(**kwargs): - kwargs["with_metadata"] = True - return load_jsons_from_dir(project_presets_path, **kwargs) - - def project_preset_overrides(project_name, **kwargs): project_configs_path = os.environ.get("PYPE_PROJECT_CONFIGS") if project_name and project_configs_path: From 8f7509d8249a6cc99ce59c9b0b0a1762cb3eab43 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 20 Aug 2020 11:49:17 +0100 Subject: [PATCH 429/947] celaction last workfile was not detecting last workfile version properly --- pype/hooks/celaction/prelaunch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 58b1ac8a23..c8541a9bc3 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -57,8 +57,8 @@ class CelactionPrelaunchHook(PypeHook): self.log.info(f"Work dir is: `{workdir}`") # get last version of workfile - workfile_last = get_last_version_from_path( - workdir, workfile.split(version)) + workfile_last = env.get("AVALON_LAST_WORKFILE") + self.log.debug(f"_ workfile_last: `{workfile_last}`") if workfile_last: workfile = workfile_last From 7868d8e0165cdf8e98ae61df1fb636b16ce5129a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 20 Aug 2020 13:22:17 +0200 Subject: [PATCH 430/947] allow thumbnails from single frame renders --- pype/plugins/global/publish/extract_jpeg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index ae74370b06..2ec97759db 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -48,7 +48,9 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): continue if not isinstance(repre['files'], (list, tuple)): - continue + input_file = repre['files'] + else: + input_file = repre['files'][0] stagingdir = os.path.normpath(repre.get("stagingDir")) input_file = repre['files'][0] From 5df17ed2939be3d780477527cd758630adeb0319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 20 Aug 2020 13:34:37 +0200 Subject: [PATCH 431/947] delete forgotten line --- pype/plugins/global/publish/extract_jpeg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index 2ec97759db..333c2ec852 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -53,7 +53,6 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): input_file = repre['files'][0] stagingdir = os.path.normpath(repre.get("stagingDir")) - input_file = repre['files'][0] # input_file = ( # collections[0].format('{head}{padding}{tail}') % start From b3f279c3ba57061b037777000bb341603807f015 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Aug 2020 14:59:26 +0200 Subject: [PATCH 432/947] return extract thumbnail to SP --- .../publish/extract_thumbnail.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 pype/plugins/standalonepublisher/publish/extract_thumbnail.py diff --git a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py new file mode 100644 index 0000000000..cddc9c3a82 --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py @@ -0,0 +1,122 @@ +import os +import tempfile +import subprocess +import pyblish.api +import pype.api +import pype.lib + + +class ExtractThumbnailSP(pyblish.api.InstancePlugin): + """Extract jpeg thumbnail from component input from standalone publisher + + Uses jpeg file from component if possible (when single or multiple jpegs + are loaded to component selected as thumbnail) otherwise extracts from + input file/s single jpeg to temp. + """ + + label = "Extract Thumbnail SP" + hosts = ["standalonepublisher"] + order = pyblish.api.ExtractorOrder + + # Presetable attribute + ffmpeg_args = None + + def process(self, instance): + repres = instance.data.get('representations') + if not repres: + return + + thumbnail_repre = None + for repre in repres: + if repre.get("thumbnail"): + thumbnail_repre = repre + break + + if not thumbnail_repre: + return + + files = thumbnail_repre.get("files") + if not files: + return + + if isinstance(files, list): + files_len = len(files) + file = str(files[0]) + else: + files_len = 1 + file = files + + is_jpeg = False + if file.endswith(".jpeg") or file.endswith(".jpg"): + is_jpeg = True + + if is_jpeg and files_len == 1: + # skip if already is single jpeg file + return + + elif is_jpeg: + # use first frame as thumbnail if is sequence of jpegs + full_thumbnail_path = file + self.log.info( + "For thumbnail is used file: {}".format(full_thumbnail_path) + ) + + else: + # Convert to jpeg if not yet + full_input_path = os.path.join(thumbnail_repre["stagingDir"], file) + self.log.info("input {}".format(full_input_path)) + + full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] + self.log.info("output {}".format(full_thumbnail_path)) + + ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(ffmpeg_path) + # override file if already exists + jpeg_items.append("-y") + # add input filters from peresets + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format(full_input_path)) + # extract only single file + jpeg_items.append("-vframes 1") + + jpeg_items.extend(ffmpeg_args.get("output") or []) + + # output file + jpeg_items.append(full_thumbnail_path) + + subprocess_jpeg = " ".join(jpeg_items) + + # run subprocess + self.log.debug("Executing: {}".format(subprocess_jpeg)) + subprocess.Popen( + subprocess_jpeg, + stdout=subprocess.PIPE, + shell=True + ) + + # remove thumbnail key from origin repre + thumbnail_repre.pop("thumbnail") + + filename = os.path.basename(full_thumbnail_path) + staging_dir = os.path.dirname(full_thumbnail_path) + + # create new thumbnail representation + representation = { + 'name': 'jpg', + 'ext': 'jpg', + 'files': filename, + "stagingDir": staging_dir, + "thumbnail": True, + "tags": [] + } + + # # add Delete tag when temp file was rendered + # if not is_jpeg: + # representation["tags"].append("delete") + + instance.data["representations"].append(representation) From 6516e8536162a6d9aa9f662293be9568914015be Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Aug 2020 14:59:46 +0200 Subject: [PATCH 433/947] remove add_representation --- .../publish/extract_shot_data.py | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/extract_shot_data.py b/pype/plugins/standalonepublisher/publish/extract_shot_data.py index 6cbc2c7882..c39247d6d6 100644 --- a/pype/plugins/standalonepublisher/publish/extract_shot_data.py +++ b/pype/plugins/standalonepublisher/publish/extract_shot_data.py @@ -13,7 +13,6 @@ class ExtractShotData(pype.api.Extractor): families = ["review", "audio"] # presets - add_representation = None # ".jpeg" def process(self, instance): representation = instance.data.get("representations") @@ -68,7 +67,7 @@ class ExtractShotData(pype.api.Extractor): self.log.info(f"Processing: {args}") ffmpeg_args = " ".join(args) - output = pype.api.subprocess(ffmpeg_args) + output = pype.api.subprocess(ffmpeg_args, shell=True) self.log.info(output) repr = { @@ -90,34 +89,4 @@ class ExtractShotData(pype.api.Extractor): instance.data["representations"].append(repr) - if self.add_representation: - # Generate jpegs. - clip_img_sequence = os.path.join( - staging_dir, instance.data["name"] + ".%04d.jpeg" - ) - args = [ - ffmpeg_path, "-i", - f"\"{clip_trimed_path}\"", - f"\"{clip_img_sequence}\"" - ] - self.log.info(f"Processing: {args}") - output = pype.lib._subprocess(args) - self.log.info(output) - - # collect jpeg sequence if editorial data for publish - # are image sequence - collection = clique.Collection( - head=instance.data["name"] + ".", tail='.jpeg', padding=4 - ) - for f in os.listdir(staging_dir): - if collection.match(f): - collection.add(f) - - instance.data["representations"].append({ - "name": "jpeg", - "ext": "jpeg", - "files": list(collection), - "stagingDir": staging_dir - }) - self.log.debug(f"Instance data: {pformat(instance.data)}") From f5cb0b04891f367ccdf7bc07270259b4ac22ec2a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Aug 2020 15:00:07 +0200 Subject: [PATCH 434/947] fix padding with start frame 0 --- pype/plugins/global/publish/integrate_new.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 4510fc2d04..28a52e4353 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -351,7 +351,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): index_frame_start = None - if repre.get("frameStart"): + if repre.get("frameStart") is not None: frame_start_padding = int( anatomy.templates["render"].get( "frame_padding", @@ -378,7 +378,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_padding = src_padding_exp % i - if index_frame_start: + if index_frame_start is not None: dst_padding_exp = "%0{}d".format(frame_start_padding) dst_padding = dst_padding_exp % index_frame_start index_frame_start += 1 From 9876bfa4cf0d6270f74f2fa16bfb5206b2910c0b Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Aug 2020 15:00:29 +0200 Subject: [PATCH 435/947] add option to remove outputName from template data --- pype/plugins/global/publish/extract_review.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index ac8766f5a7..2807376d54 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -193,6 +193,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # Force to pop these key if are in new repre new_repre.pop("preview", None) new_repre.pop("thumbnail", None) + if "clean_name" in new_repre.get("tags", []): + new_repre.pop("outputName") # adding representation self.log.debug( @@ -1528,6 +1530,8 @@ class ExtractReview(pyblish.api.InstancePlugin): for repre in representations_new: if "delete" in repre.get("tags", []): representations_new.remove(repre) + if "clean_name" in repre.get("tags", []): + repre_new.pop("outputName") instance.data.update({ "reviewToWidth": self.to_width, From 3a69b9956d6d9ca82a99edb77c8ad52fa6b11084 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 15:15:10 +0200 Subject: [PATCH 436/947] moved resources to widgets folder --- .../{ => widgets}/resources/__init__.py | 0 .../{ => widgets}/resources/edit.svg | 0 .../{ => widgets}/resources/file.png | Bin .../{ => widgets}/resources/files.png | Bin .../{ => widgets}/resources/houdini.png | Bin .../{ => widgets}/resources/image_file.png | Bin .../{ => widgets}/resources/image_files.png | Bin .../{ => widgets}/resources/information.svg | 0 .../{ => widgets}/resources/maya.png | Bin .../{ => widgets}/resources/menu.png | Bin .../{ => widgets}/resources/menu_disabled.png | Bin .../{ => widgets}/resources/menu_hover.png | Bin .../{ => widgets}/resources/menu_pressed.png | Bin .../{ => widgets}/resources/menu_pressed_hover.png | Bin .../{ => widgets}/resources/nuke.png | Bin .../{ => widgets}/resources/premiere.png | Bin .../{ => widgets}/resources/trash.png | Bin .../{ => widgets}/resources/trash_disabled.png | Bin .../{ => widgets}/resources/trash_hover.png | Bin .../{ => widgets}/resources/trash_pressed.png | Bin .../{ => widgets}/resources/trash_pressed_hover.png | Bin .../{ => widgets}/resources/video_file.png | Bin 22 files changed, 0 insertions(+), 0 deletions(-) rename pype/tools/standalonepublish/{ => widgets}/resources/__init__.py (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/edit.svg (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/file.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/files.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/houdini.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/image_file.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/image_files.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/information.svg (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/maya.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/menu.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/menu_disabled.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/menu_hover.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/menu_pressed.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/menu_pressed_hover.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/nuke.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/premiere.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/trash.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/trash_disabled.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/trash_hover.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/trash_pressed.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/trash_pressed_hover.png (100%) rename pype/tools/standalonepublish/{ => widgets}/resources/video_file.png (100%) diff --git a/pype/tools/standalonepublish/resources/__init__.py b/pype/tools/standalonepublish/widgets/resources/__init__.py similarity index 100% rename from pype/tools/standalonepublish/resources/__init__.py rename to pype/tools/standalonepublish/widgets/resources/__init__.py diff --git a/pype/tools/standalonepublish/resources/edit.svg b/pype/tools/standalonepublish/widgets/resources/edit.svg similarity index 100% rename from pype/tools/standalonepublish/resources/edit.svg rename to pype/tools/standalonepublish/widgets/resources/edit.svg diff --git a/pype/tools/standalonepublish/resources/file.png b/pype/tools/standalonepublish/widgets/resources/file.png similarity index 100% rename from pype/tools/standalonepublish/resources/file.png rename to pype/tools/standalonepublish/widgets/resources/file.png diff --git a/pype/tools/standalonepublish/resources/files.png b/pype/tools/standalonepublish/widgets/resources/files.png similarity index 100% rename from pype/tools/standalonepublish/resources/files.png rename to pype/tools/standalonepublish/widgets/resources/files.png diff --git a/pype/tools/standalonepublish/resources/houdini.png b/pype/tools/standalonepublish/widgets/resources/houdini.png similarity index 100% rename from pype/tools/standalonepublish/resources/houdini.png rename to pype/tools/standalonepublish/widgets/resources/houdini.png diff --git a/pype/tools/standalonepublish/resources/image_file.png b/pype/tools/standalonepublish/widgets/resources/image_file.png similarity index 100% rename from pype/tools/standalonepublish/resources/image_file.png rename to pype/tools/standalonepublish/widgets/resources/image_file.png diff --git a/pype/tools/standalonepublish/resources/image_files.png b/pype/tools/standalonepublish/widgets/resources/image_files.png similarity index 100% rename from pype/tools/standalonepublish/resources/image_files.png rename to pype/tools/standalonepublish/widgets/resources/image_files.png diff --git a/pype/tools/standalonepublish/resources/information.svg b/pype/tools/standalonepublish/widgets/resources/information.svg similarity index 100% rename from pype/tools/standalonepublish/resources/information.svg rename to pype/tools/standalonepublish/widgets/resources/information.svg diff --git a/pype/tools/standalonepublish/resources/maya.png b/pype/tools/standalonepublish/widgets/resources/maya.png similarity index 100% rename from pype/tools/standalonepublish/resources/maya.png rename to pype/tools/standalonepublish/widgets/resources/maya.png diff --git a/pype/tools/standalonepublish/resources/menu.png b/pype/tools/standalonepublish/widgets/resources/menu.png similarity index 100% rename from pype/tools/standalonepublish/resources/menu.png rename to pype/tools/standalonepublish/widgets/resources/menu.png diff --git a/pype/tools/standalonepublish/resources/menu_disabled.png b/pype/tools/standalonepublish/widgets/resources/menu_disabled.png similarity index 100% rename from pype/tools/standalonepublish/resources/menu_disabled.png rename to pype/tools/standalonepublish/widgets/resources/menu_disabled.png diff --git a/pype/tools/standalonepublish/resources/menu_hover.png b/pype/tools/standalonepublish/widgets/resources/menu_hover.png similarity index 100% rename from pype/tools/standalonepublish/resources/menu_hover.png rename to pype/tools/standalonepublish/widgets/resources/menu_hover.png diff --git a/pype/tools/standalonepublish/resources/menu_pressed.png b/pype/tools/standalonepublish/widgets/resources/menu_pressed.png similarity index 100% rename from pype/tools/standalonepublish/resources/menu_pressed.png rename to pype/tools/standalonepublish/widgets/resources/menu_pressed.png diff --git a/pype/tools/standalonepublish/resources/menu_pressed_hover.png b/pype/tools/standalonepublish/widgets/resources/menu_pressed_hover.png similarity index 100% rename from pype/tools/standalonepublish/resources/menu_pressed_hover.png rename to pype/tools/standalonepublish/widgets/resources/menu_pressed_hover.png diff --git a/pype/tools/standalonepublish/resources/nuke.png b/pype/tools/standalonepublish/widgets/resources/nuke.png similarity index 100% rename from pype/tools/standalonepublish/resources/nuke.png rename to pype/tools/standalonepublish/widgets/resources/nuke.png diff --git a/pype/tools/standalonepublish/resources/premiere.png b/pype/tools/standalonepublish/widgets/resources/premiere.png similarity index 100% rename from pype/tools/standalonepublish/resources/premiere.png rename to pype/tools/standalonepublish/widgets/resources/premiere.png diff --git a/pype/tools/standalonepublish/resources/trash.png b/pype/tools/standalonepublish/widgets/resources/trash.png similarity index 100% rename from pype/tools/standalonepublish/resources/trash.png rename to pype/tools/standalonepublish/widgets/resources/trash.png diff --git a/pype/tools/standalonepublish/resources/trash_disabled.png b/pype/tools/standalonepublish/widgets/resources/trash_disabled.png similarity index 100% rename from pype/tools/standalonepublish/resources/trash_disabled.png rename to pype/tools/standalonepublish/widgets/resources/trash_disabled.png diff --git a/pype/tools/standalonepublish/resources/trash_hover.png b/pype/tools/standalonepublish/widgets/resources/trash_hover.png similarity index 100% rename from pype/tools/standalonepublish/resources/trash_hover.png rename to pype/tools/standalonepublish/widgets/resources/trash_hover.png diff --git a/pype/tools/standalonepublish/resources/trash_pressed.png b/pype/tools/standalonepublish/widgets/resources/trash_pressed.png similarity index 100% rename from pype/tools/standalonepublish/resources/trash_pressed.png rename to pype/tools/standalonepublish/widgets/resources/trash_pressed.png diff --git a/pype/tools/standalonepublish/resources/trash_pressed_hover.png b/pype/tools/standalonepublish/widgets/resources/trash_pressed_hover.png similarity index 100% rename from pype/tools/standalonepublish/resources/trash_pressed_hover.png rename to pype/tools/standalonepublish/widgets/resources/trash_pressed_hover.png diff --git a/pype/tools/standalonepublish/resources/video_file.png b/pype/tools/standalonepublish/widgets/resources/video_file.png similarity index 100% rename from pype/tools/standalonepublish/resources/video_file.png rename to pype/tools/standalonepublish/widgets/resources/video_file.png From 1e101c1fbc408e7fe77809dca3385b14e16fdea6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 15:15:28 +0200 Subject: [PATCH 437/947] PngFactory do not create Qt object on init --- .../widgets/widget_component_item.py | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/pype/tools/standalonepublish/widgets/widget_component_item.py b/pype/tools/standalonepublish/widgets/widget_component_item.py index dd838075e3..3850d68b96 100644 --- a/pype/tools/standalonepublish/widgets/widget_component_item.py +++ b/pype/tools/standalonepublish/widgets/widget_component_item.py @@ -1,6 +1,6 @@ import os from Qt import QtCore, QtGui, QtWidgets -from pype.resources import get_resource +from .resources import get_resource from avalon import style @@ -353,27 +353,37 @@ class LightingButton(QtWidgets.QPushButton): class PngFactory: - png_names = { - "trash": { - "normal": QtGui.QIcon(get_resource("trash.png")), - "hover": QtGui.QIcon(get_resource("trash_hover.png")), - "pressed": QtGui.QIcon(get_resource("trash_pressed.png")), - "pressed_hover": QtGui.QIcon( - get_resource("trash_pressed_hover.png") - ), - "disabled": QtGui.QIcon(get_resource("trash_disabled.png")) - }, + png_names = None - "menu": { - "normal": QtGui.QIcon(get_resource("menu.png")), - "hover": QtGui.QIcon(get_resource("menu_hover.png")), - "pressed": QtGui.QIcon(get_resource("menu_pressed.png")), - "pressed_hover": QtGui.QIcon( - get_resource("menu_pressed_hover.png") - ), - "disabled": QtGui.QIcon(get_resource("menu_disabled.png")) + @classmethod + def init(cls): + cls.png_names = { + "trash": { + "normal": QtGui.QIcon(get_resource("trash.png")), + "hover": QtGui.QIcon(get_resource("trash_hover.png")), + "pressed": QtGui.QIcon(get_resource("trash_pressed.png")), + "pressed_hover": QtGui.QIcon( + get_resource("trash_pressed_hover.png") + ), + "disabled": QtGui.QIcon(get_resource("trash_disabled.png")) + }, + + "menu": { + "normal": QtGui.QIcon(get_resource("menu.png")), + "hover": QtGui.QIcon(get_resource("menu_hover.png")), + "pressed": QtGui.QIcon(get_resource("menu_pressed.png")), + "pressed_hover": QtGui.QIcon( + get_resource("menu_pressed_hover.png") + ), + "disabled": QtGui.QIcon(get_resource("menu_disabled.png")) + } } - } + + @classmethod + def get_png(cls, name): + if cls.png_names is None: + cls.init() + return cls.png_names.get(name) class PngButton(QtWidgets.QPushButton): @@ -406,7 +416,7 @@ class PngButton(QtWidgets.QPushButton): png_dict = {} if name: - png_dict = PngFactory.png_names.get(name) or {} + png_dict = PngFactory.get_png(name) or {} if not png_dict: print(( "WARNING: There is not set icon with name \"{}\"" From f41bf542e8adddf372d142a3c97f912cb060cc70 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Aug 2020 15:59:16 +0200 Subject: [PATCH 438/947] simplify default --- .../standalonepublisher/publish/collect_clip_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py index 3d9773d0b2..a7af8df143 100644 --- a/pype/plugins/standalonepublisher/publish/collect_clip_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_clip_instances.py @@ -18,15 +18,15 @@ class CollectClipInstances(pyblish.api.InstancePlugin): "referenceMain": { "family": "review", "families": ["review", "ftrack"], - "ftrackFamily": "review", + # "ftrackFamily": "review", "extension": ".mp4" }, "audioMain": { "family": "audio", "families": ["ftrack"], - "ftrackFamily": "audio", + # "ftrackFamily": "audio", "extension": ".wav", - "version": 1 + # "version": 1 }, "shotMain": { "family": "shot", From 016f529a90ef5ece370a1e6f1636805cc06c4876 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Aug 2020 15:59:33 +0200 Subject: [PATCH 439/947] add reference family to ftrack --- pype/plugins/ftrack/publish/integrate_ftrack_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index f5d7689678..9ef82cbc4b 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -30,7 +30,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): 'audio': 'audio', 'workfile': 'scene', 'animation': 'cache', - 'image': 'img' + 'image': 'img', + 'reference': 'reference' } def process(self, instance): From 4c6e5fefb415eab87ee52fd3b0bceb416f0643bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 16:05:03 +0200 Subject: [PATCH 440/947] implemented action for pushing frameStart and frameEnd values to task specific custom attributes --- .../action_push_frame_values_to_task.py | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 pype/modules/ftrack/actions/action_push_frame_values_to_task.py diff --git a/pype/modules/ftrack/actions/action_push_frame_values_to_task.py b/pype/modules/ftrack/actions/action_push_frame_values_to_task.py new file mode 100644 index 0000000000..3037695452 --- /dev/null +++ b/pype/modules/ftrack/actions/action_push_frame_values_to_task.py @@ -0,0 +1,245 @@ +import json +import collections +import ftrack_api +from pype.modules.ftrack.lib import BaseAction, statics_icon + + +class PushFrameValuesToTaskAction(BaseAction): + """Action for testing purpose or as base for new actions.""" + + identifier = "admin.push_frame_values_to_task" + label = "Pype Admin" + variant = "- Push Frame values to Task" + role_list = ["Pypeclub", "Administrator", "Project Manager"] + icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + + entities_query = ( + "select id, name, parent_id, link" + " from TypedContext where project_id is \"{}\"" + ) + cust_attrs_query = ( + "select id, key, object_type_id, is_hierarchical, default" + " from CustomAttributeConfiguration" + " where key in ({})" + ) + cust_attr_value_query = ( + "select value, entity_id from CustomAttributeValue" + " where entity_id in ({}) and configuration_id in ({})" + ) + custom_attribute_keys = ["frameStart", "frameEnd"] + + def discover(self, session, entities, event): + return True + + def launch(self, session, entities, event): + task_attrs_by_key, hier_attrs = self.frame_attributes(session) + missing_keys = [ + key + for key in self.custom_attribute_keys + if key not in task_attrs_by_key + ] + if missing_keys: + if len(missing_keys) == 1: + sub_msg = " \"{}\"".format(missing_keys[0]) + else: + sub_msg = "s {}".format(", ".join([ + "\"{}\"".format(key) + for key in missing_keys + ])) + + msg = "Missing Task's custom attribute{}.".format(sub_msg) + self.log.warning(msg) + return { + "success": False, + "message": msg + } + + self.log.debug("{}: Creating job".format(self.label)) + + user_entity = session.query( + "User where id is {}".format(event["source"]["user"]["id"]) + ).one() + job = session.create("Job", { + "user": user_entity, + "status": "running", + "data": json.dumps({ + "description": "Propagation of Frame attribute values to task." + }) + }) + session.commit() + + try: + project_entity = self.get_project_from_entity(entities[0]) + result = self.propagate_values( + session, + tuple(task_attrs_by_key.values()), + hier_attrs, + project_entity + ) + job["status"] = "done" + session.commit() + + return result + + except Exception: + session.rollback() + job["status"] = "failed" + session.commit() + + msg = "Pushing Custom attribute values to task Failed" + self.log.warning(msg, exc_info=True) + return { + "success": False, + "message": msg + } + + finally: + if job["status"] == "running": + job["status"] = "failed" + session.commit() + + def frame_attributes(self, session): + task_object_type = session.query( + "ObjectType where name is \"Task\"" + ).one() + + attr_names = self.custom_attribute_keys + if isinstance(attr_names, str): + attr_names = [attr_names] + + joined_keys = ",".join([ + "\"{}\"".format(attr_name) + for attr_name in attr_names + ]) + + attribute_entities = session.query( + self.cust_attrs_query.format(joined_keys) + ).all() + + hier_attrs = [] + task_attrs = {} + for attr in attribute_entities: + attr_key = attr["key"] + if attr["is_hierarchical"]: + hier_attrs.append(attr) + elif attr["object_type_id"] == task_object_type["id"]: + task_attrs[attr_key] = attr + return task_attrs, hier_attrs + + def join_keys(self, items): + return ",".join(["\"{}\"".format(item) for item in items]) + + def propagate_values( + self, session, task_attrs, hier_attrs, project_entity + ): + self.log.debug("Querying project's entities \"{}\".".format( + project_entity["full_name"] + )) + entities = session.query( + self.entities_query.format(project_entity["id"]) + ).all() + + self.log.debug("Filtering Task entities.") + task_entities_by_parent_id = collections.defaultdict(list) + for entity in entities: + if entity.entity_type.lower() == "task": + task_entities_by_parent_id[entity["parent_id"]].append(entity) + + self.log.debug("Getting Custom attribute values from tasks' parents.") + hier_values_by_entity_id = self.get_hier_values( + session, + hier_attrs, + list(task_entities_by_parent_id.keys()) + ) + + self.log.debug("Setting parents' values to task.") + self.set_task_attr_values( + session, + task_entities_by_parent_id, + hier_values_by_entity_id, + task_attrs + ) + + return True + + def get_hier_values(self, session, hier_attrs, focus_entity_ids): + joined_entity_ids = self.join_keys(focus_entity_ids) + hier_attr_ids = self.join_keys( + tuple(hier_attr["id"] for hier_attr in hier_attrs) + ) + hier_attrs_key_by_id = { + hier_attr["id"]: hier_attr["key"] + for hier_attr in hier_attrs + } + call_expr = [{ + "action": "query", + "expression": self.cust_attr_value_query.format( + joined_entity_ids, hier_attr_ids + ) + }] + if hasattr(session, "call"): + [values] = session.call(call_expr) + else: + [values] = session._call(call_expr) + + values_per_entity_id = {} + for item in values["data"]: + entity_id = item["entity_id"] + key = hier_attrs_key_by_id[item["configuration_id"]] + + if entity_id not in values_per_entity_id: + values_per_entity_id[entity_id] = {} + value = item["value"] + if value is not None: + values_per_entity_id[entity_id][key] = value + + output = {} + for entity_id in focus_entity_ids: + value = values_per_entity_id.get(entity_id) + if value: + output[entity_id] = value + + return output + + def set_task_attr_values( + self, + session, + task_entities_by_parent_id, + hier_values_by_entity_id, + task_attrs + ): + task_attr_ids_by_key = { + attr["key"]: attr["id"] + for attr in task_attrs + } + + total_parents = len(hier_values_by_entity_id) + idx = 1 + for parent_id, values in hier_values_by_entity_id.items(): + self.log.info(( + "[{}/{}] {} Processing values to children. Values: {}" + ).format(idx, total_parents, parent_id, values)) + + task_entities = task_entities_by_parent_id[parent_id] + for key, value in values.items(): + for task_entity in task_entities: + _entity_key = collections.OrderedDict({ + "configuration_id": task_attr_ids_by_key[key], + "entity_id": task_entity["id"] + }) + + session.recorded_operations.push( + ftrack_api.operation.UpdateEntityOperation( + "ContextCustomAttributeValue", + _entity_key, + "value", + ftrack_api.symbol.NOT_SET, + value + ) + ) + session.commit() + idx += 1 + + +def register(session, plugins_presets={}): + PushFrameValuesToTask(session, plugins_presets).register() From 1431930e6af89df656cced3572598907d13525ad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 16:05:13 +0200 Subject: [PATCH 441/947] implemented event handler for pushing frameStart and frameEnd values to task specific custom attributes --- .../events/event_push_frame_values_to_task.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 pype/modules/ftrack/events/event_push_frame_values_to_task.py diff --git a/pype/modules/ftrack/events/event_push_frame_values_to_task.py b/pype/modules/ftrack/events/event_push_frame_values_to_task.py new file mode 100644 index 0000000000..dd5c5911ec --- /dev/null +++ b/pype/modules/ftrack/events/event_push_frame_values_to_task.py @@ -0,0 +1,148 @@ +import collections +import ftrack_api +from pype.modules.ftrack import BaseEvent + + +class PushFrameValuesToTaskEvent(BaseEvent): + """Action for testing purpose or as base for new actions.""" + cust_attrs_query = ( + "select id, key, object_type_id, is_hierarchical, default" + " from CustomAttributeConfiguration" + " where key in ({}) and object_type_id = {}" + ) + + # Ignore event handler by default + ignore_me = True + + interest_attributes = ["frameStart", "frameEnd"] + _cached_task_object_id = None + + @classmethod + def task_object_id(cls, session): + if cls._cached_task_object_id is None: + task_object_type = session.query( + "ObjectType where name is \"Task\"" + ).one() + cls._cached_task_object_id = task_object_type["id"] + return cls._cached_task_object_id + + def extract_interesting_data(self, session, event): + # Filter if event contain relevant data + entities_info = event["data"].get("entities") + if not entities_info: + return + + interesting_data = {} + for entity_info in entities_info: + # Care only about tasks + if entity_info.get("entityType") != "task": + continue + + # Care only about changes of status + changes = entity_info.get("changes") or {} + if not changes: + continue + + # Care only about changes if specific keys + entity_changes = {} + for key in self.interest_attributes: + if key in changes: + entity_changes[key] = changes[key]["new"] + + if not entity_changes: + continue + + # Do not care about "Task" entity_type + task_object_id = self.task_object_id(session) + if entity_info.get("objectTypeId") == task_object_id: + continue + + interesting_data[entity_info["entityId"]] = entity_changes + return interesting_data + + def join_keys(self, keys): + return ",".join(["\"{}\"".format(key) for key in keys]) + + def get_task_entities(self, session, entities_info): + return session.query( + "Task where parent_id in ({})".format( + self.join_keys(entities_info.keys()) + ) + ).all() + + def task_attrs(self, session): + return session.query(self.cust_attrs_query.format( + self.join_keys(self.interest_attributes), + self.task_object_id(session) + )).all() + + def launch(self, session, event): + interesting_data = self.extract_interesting_data(session, event) + if not interesting_data: + return + + task_entities = self.get_task_entities(session, interesting_data) + if not task_entities: + return + + task_attrs = self.task_attrs(session) + if not task_attrs: + self.log.warning(( + "There is not created Custom Attributes {}" + " for \"Task\" entity type." + ).format(self.join_keys(self.interest_attributes))) + return + + task_attr_id_by_key = { + attr["key"]: attr["id"] + for attr in task_attrs + } + task_entities_by_parent_id = collections.defaultdict(list) + for task_entity in task_entities: + task_entities_by_parent_id[task_entity["parent_id"]].append( + task_entity + ) + + for parent_id, values in interesting_data.items(): + task_entities = task_entities_by_parent_id[parent_id] + for key, value in values.items(): + changed_ids = [] + for task_entity in task_entities: + task_id = task_entity["id"] + changed_ids.append(task_id) + + entity_key = collections.OrderedDict({ + "configuration_id": task_attr_id_by_key[key], + "entity_id": task_id + }) + if value is None: + op = ftrack_api.operation.DeleteEntityOperation( + "CustomAttributeValue", + entity_key + ) + else: + op = ftrack_api.operation.UpdateEntityOperation( + "ContextCustomAttributeValue", + entity_key, + "value", + ftrack_api.symbol.NOT_SET, + value + ) + + session.recorded_operations.push(op) + self.log.info(( + "Changing Custom Attribute \"{}\" to value" + " \"{}\" on entities: {}" + ).format(key, value, self.join_keys(changed_ids))) + try: + session.commit() + except Exception: + session.rollback() + self.log.warning( + "Changing of values failed.", + exc_info=True + ) + + +def register(session, plugins_presets): + PushFrameValuesToTaskEvent(session, plugins_presets).register() From 516fafbfec822e8b5f2957bdd108f157412a5963 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 16:39:16 +0200 Subject: [PATCH 442/947] moved action to server --- .../{actions => events}/action_push_frame_values_to_task.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/modules/ftrack/{actions => events}/action_push_frame_values_to_task.py (100%) diff --git a/pype/modules/ftrack/actions/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py similarity index 100% rename from pype/modules/ftrack/actions/action_push_frame_values_to_task.py rename to pype/modules/ftrack/events/action_push_frame_values_to_task.py From 8ae527a154c24d0b0e5634546d759f421f0c0426 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 16:39:47 +0200 Subject: [PATCH 443/947] action converted to server action --- .../action_push_frame_values_to_task.py | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index 3037695452..bd036411ac 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -1,7 +1,7 @@ import json import collections import ftrack_api -from pype.modules.ftrack.lib import BaseAction, statics_icon +from pype.modules.ftrack.lib import BaseAction class PushFrameValuesToTaskAction(BaseAction): @@ -10,8 +10,6 @@ class PushFrameValuesToTaskAction(BaseAction): identifier = "admin.push_frame_values_to_task" label = "Pype Admin" variant = "- Push Frame values to Task" - role_list = ["Pypeclub", "Administrator", "Project Manager"] - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") entities_query = ( "select id, name, parent_id, link" @@ -26,12 +24,56 @@ class PushFrameValuesToTaskAction(BaseAction): "select value, entity_id from CustomAttributeValue" " where entity_id in ({}) and configuration_id in ({})" ) - custom_attribute_keys = ["frameStart", "frameEnd"] + custom_attribute_keys = {"frameStart", "frameEnd"} + discover_role_list = {"Pypeclub", "Administrator", "Project Manager"} + + def register(self): + modified_role_names = set() + for role_name in self.discover_role_list: + modified_role_names.add(role_name.lower()) + self.discover_role_list = modified_role_names + + self.session.event_hub.subscribe( + "topic=ftrack.action.discover", + self._discover, + priority=self.priority + ) + + launch_subscription = ( + "topic=ftrack.action.launch and data.actionIdentifier={0}" + ).format(self.identifier) + self.session.event_hub.subscribe(launch_subscription, self._launch) def discover(self, session, entities, event): - return True + """ Validation """ + # Check if selection is valid + valid_selection = False + for ent in event["data"]["selection"]: + # Ignore entities that are not tasks or projects + if ent["entityType"].lower() in ["show", "task"]: + valid_selection = True + break + + if not valid_selection: + return False + + # Get user and check his roles + user_id = event.get("source", {}).get("user", {}).get("id") + if not user_id: + return False + + user = session.query("User where id is \"{}\"".format(user_id)).first() + if not user: + return False + + for role in user["user_security_roles"]: + lowered_role = role["security_role"]["name"].lower() + if lowered_role in self.discover_role_list: + return True + return False def launch(self, session, entities, event): + # TODO this can be threaded task_attrs_by_key, hier_attrs = self.frame_attributes(session) missing_keys = [ key @@ -103,15 +145,7 @@ class PushFrameValuesToTaskAction(BaseAction): "ObjectType where name is \"Task\"" ).one() - attr_names = self.custom_attribute_keys - if isinstance(attr_names, str): - attr_names = [attr_names] - - joined_keys = ",".join([ - "\"{}\"".format(attr_name) - for attr_name in attr_names - ]) - + joined_keys = self.join_keys(self.custom_attribute_keys) attribute_entities = session.query( self.cust_attrs_query.format(joined_keys) ).all() @@ -242,4 +276,4 @@ class PushFrameValuesToTaskAction(BaseAction): def register(session, plugins_presets={}): - PushFrameValuesToTask(session, plugins_presets).register() + PushFrameValuesToTaskAction(session, plugins_presets).register() From 63c2cf6a41ca8d66cfffca914d6933c28d32b920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 20 Aug 2020 16:39:57 +0200 Subject: [PATCH 444/947] support for different tile order in vray --- pype/plugins/maya/publish/submit_maya_deadline.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index d9ee7f9646..747d2727b7 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -65,7 +65,9 @@ payload_skeleton = { } -def _format_tiles(filename, index, tiles_x, tiles_y, width, height, prefix): +def _format_tiles( + filename, index, tiles_x, tiles_y, + width, height, prefix, origin="blc"): """Generate tile entries for Deadline tile job. Returns two dictionaries - one that can be directly used in Deadline @@ -142,7 +144,11 @@ def _format_tiles(filename, index, tiles_x, tiles_y, width, height, prefix): cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - cfg["Tile{}Y".format(tile)] = (tile_y - 1) * h_space + if origin == "blc": + cfg["Tile{}Y".format(tile)] = (tile_y - 1) * h_space + else: + cfg["Tile{}Y".format(tile)] = int(height) - ((tile_y - 1) * h_space) # noqa: E501 + cfg["Tile{}Width".format(tile)] = tile_x * w_space cfg["Tile{}Height".format(tile)] = tile_y * h_space @@ -549,6 +555,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): assembly_payload["JobInfo"].update(output_filenames) assembly_payload["JobInfo"]["Priority"] = self._instance.data.get( "priority", 50) + assembly_payload["JobInfo"]["UserName"] = deadline_user frame_payloads = [] assembly_payloads = [] @@ -620,6 +627,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): REPL_FRAME_NUMBER, "\\1{}\\3".format("#" * len(frame)), file) + new_assembly_payload["PluginInfo"]["Renderer"] = self._instance.data["renderer"] # noqa: E501 new_assembly_payload["JobInfo"]["ExtraInfo0"] = frame_jobs[frame] # noqa: E501 new_assembly_payload["JobInfo"]["ExtraInfo1"] = file assembly_payloads.append(new_assembly_payload) From a37da37bd15c6e5aca9c37ea56a57eb163359f11 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 16:42:56 +0200 Subject: [PATCH 445/947] commit all changes at once --- .../modules/ftrack/events/action_push_frame_values_to_task.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index bd036411ac..4f0c7ffeb7 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -253,6 +253,7 @@ class PushFrameValuesToTaskAction(BaseAction): self.log.info(( "[{}/{}] {} Processing values to children. Values: {}" ).format(idx, total_parents, parent_id, values)) + idx += 1 task_entities = task_entities_by_parent_id[parent_id] for key, value in values.items(): @@ -271,8 +272,7 @@ class PushFrameValuesToTaskAction(BaseAction): value ) ) - session.commit() - idx += 1 + session.commit() def register(session, plugins_presets={}): From 97b42d8703150038feb6833832c9da0d39df40ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 16:46:35 +0200 Subject: [PATCH 446/947] ignore action by default --- pype/modules/ftrack/events/action_push_frame_values_to_task.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index 4f0c7ffeb7..5b7da8bebb 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -7,6 +7,9 @@ from pype.modules.ftrack.lib import BaseAction class PushFrameValuesToTaskAction(BaseAction): """Action for testing purpose or as base for new actions.""" + # Ignore event handler by default + ignore_me = True + identifier = "admin.push_frame_values_to_task" label = "Pype Admin" variant = "- Push Frame values to Task" From 07b34dec926f0135a03327d4c52aa2d2837e5199 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 17:45:59 +0200 Subject: [PATCH 447/947] show only on project --- pype/modules/ftrack/events/action_push_frame_values_to_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index 5b7da8bebb..e6276d84ac 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -53,7 +53,7 @@ class PushFrameValuesToTaskAction(BaseAction): valid_selection = False for ent in event["data"]["selection"]: # Ignore entities that are not tasks or projects - if ent["entityType"].lower() in ["show", "task"]: + if ent["entityType"].lower() == "show": valid_selection = True break From fb6de46cd6c186934cd878c6e055dadf86c89d83 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 19:05:56 +0200 Subject: [PATCH 448/947] pushing is also pushing to item itself --- .../action_push_frame_values_to_task.py | 279 ++++++++++++++---- 1 file changed, 221 insertions(+), 58 deletions(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index e6276d84ac..d88f4a1016 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -15,8 +15,8 @@ class PushFrameValuesToTaskAction(BaseAction): variant = "- Push Frame values to Task" entities_query = ( - "select id, name, parent_id, link" - " from TypedContext where project_id is \"{}\"" + "select id, name, parent_id, link from TypedContext" + " where project_id is \"{}\" and object_type_id in ({})" ) cust_attrs_query = ( "select id, key, object_type_id, is_hierarchical, default" @@ -27,7 +27,13 @@ class PushFrameValuesToTaskAction(BaseAction): "select value, entity_id from CustomAttributeValue" " where entity_id in ({}) and configuration_id in ({})" ) - custom_attribute_keys = {"frameStart", "frameEnd"} + + pushing_entity_types = {"Shot"} + hierarchical_custom_attribute_keys = {"frameStart", "frameEnd"} + custom_attribute_mapping = { + "frameStart": "fstart", + "frameEnd": "fend" + } discover_role_list = {"Pypeclub", "Administrator", "Project Manager"} def register(self): @@ -76,29 +82,6 @@ class PushFrameValuesToTaskAction(BaseAction): return False def launch(self, session, entities, event): - # TODO this can be threaded - task_attrs_by_key, hier_attrs = self.frame_attributes(session) - missing_keys = [ - key - for key in self.custom_attribute_keys - if key not in task_attrs_by_key - ] - if missing_keys: - if len(missing_keys) == 1: - sub_msg = " \"{}\"".format(missing_keys[0]) - else: - sub_msg = "s {}".format(", ".join([ - "\"{}\"".format(key) - for key in missing_keys - ])) - - msg = "Missing Task's custom attribute{}.".format(sub_msg) - self.log.warning(msg) - return { - "success": False, - "message": msg - } - self.log.debug("{}: Creating job".format(self.label)) user_entity = session.query( @@ -115,12 +98,7 @@ class PushFrameValuesToTaskAction(BaseAction): try: project_entity = self.get_project_from_entity(entities[0]) - result = self.propagate_values( - session, - tuple(task_attrs_by_key.values()), - hier_attrs, - project_entity - ) + result = self.propagate_values(session, project_entity, event) job["status"] = "done" session.commit() @@ -143,12 +121,20 @@ class PushFrameValuesToTaskAction(BaseAction): job["status"] = "failed" session.commit() - def frame_attributes(self, session): + def task_attributes(self, session): task_object_type = session.query( "ObjectType where name is \"Task\"" ).one() - joined_keys = self.join_keys(self.custom_attribute_keys) + hier_attr_names = list( + self.custom_attribute_mapping.keys() + ) + entity_type_specific_names = list( + self.custom_attribute_mapping.values() + ) + joined_keys = self.join_keys( + hier_attr_names + entity_type_specific_names + ) attribute_entities = session.query( self.cust_attrs_query.format(joined_keys) ).all() @@ -158,47 +144,139 @@ class PushFrameValuesToTaskAction(BaseAction): for attr in attribute_entities: attr_key = attr["key"] if attr["is_hierarchical"]: - hier_attrs.append(attr) + if attr_key in hier_attr_names: + hier_attrs.append(attr) elif attr["object_type_id"] == task_object_type["id"]: - task_attrs[attr_key] = attr + if attr_key in entity_type_specific_names: + task_attrs[attr_key] = attr["id"] return task_attrs, hier_attrs def join_keys(self, items): return ",".join(["\"{}\"".format(item) for item in items]) - def propagate_values( - self, session, task_attrs, hier_attrs, project_entity - ): + def propagate_values(self, session, project_entity, event): self.log.debug("Querying project's entities \"{}\".".format( project_entity["full_name"] )) - entities = session.query( - self.entities_query.format(project_entity["id"]) - ).all() + pushing_entity_types = tuple( + ent_type.lower() + for ent_type in self.pushing_entity_types + ) + destination_object_types = [] + all_object_types = session.query("ObjectType").all() + for object_type in all_object_types: + lowered_name = object_type["name"].lower() + if ( + lowered_name == "task" + or lowered_name in pushing_entity_types + ): + destination_object_types.append(object_type) + + destination_object_type_ids = tuple( + obj_type["id"] + for obj_type in destination_object_types + ) + entities = session.query(self.entities_query.format( + project_entity["id"], + self.join_keys(destination_object_type_ids) + )).all() + + entities_by_id = { + entity["id"]: entity + for entity in entities + } self.log.debug("Filtering Task entities.") task_entities_by_parent_id = collections.defaultdict(list) + non_task_entities = [] + non_task_entity_ids = [] for entity in entities: - if entity.entity_type.lower() == "task": - task_entities_by_parent_id[entity["parent_id"]].append(entity) + if entity.entity_type.lower() != "task": + non_task_entities.append(entity) + non_task_entity_ids.append(entity["id"]) + continue + + parent_id = entity["parent_id"] + if parent_id in entities_by_id: + task_entities_by_parent_id[parent_id].append(entity) + + task_attr_id_by_keys, hier_attrs = self.task_attributes(session) self.log.debug("Getting Custom attribute values from tasks' parents.") hier_values_by_entity_id = self.get_hier_values( session, hier_attrs, - list(task_entities_by_parent_id.keys()) + non_task_entity_ids ) self.log.debug("Setting parents' values to task.") - self.set_task_attr_values( + task_missing_keys = self.set_task_attr_values( session, task_entities_by_parent_id, hier_values_by_entity_id, - task_attrs + task_attr_id_by_keys ) + self.log.debug("Setting values to entities themselves.") + missing_keys_by_object_name = self.push_values_to_entities( + session, + non_task_entities, + hier_values_by_entity_id + ) + if task_missing_keys: + missing_keys_by_object_name["Task"] = task_missing_keys + if missing_keys_by_object_name: + self.report(missing_keys_by_object_name, event) return True + def report(self, missing_keys_by_object_name, event): + splitter = {"type": "label", "value": "---"} + + title = "Push Custom Attribute values report:" + + items = [] + items.append({ + "type": "label", + "value": "# Pushing values was not complete" + }) + items.append({ + "type": "label", + "value": ( + "

It was due to missing custom" + " attribute configurations for specific entity type/s." + " These configurations are not created automatically.

" + ) + }) + + log_message_items = [] + log_message_item_template = ( + "Entity type \"{}\" does not have created Custom Attribute/s: {}" + ) + for object_name, missing_attr_names in ( + missing_keys_by_object_name.items() + ): + log_message_items.append(log_message_item_template.format( + object_name, self.join_keys(missing_attr_names) + )) + + items.append(splitter) + items.append({ + "type": "label", + "value": "## Entity type: {}".format(object_name) + }) + + items.append({ + "type": "label", + "value": "

{}

".format("
".join(missing_attr_names)) + }) + + self.log.warning(( + "Couldn't finish pushing attribute values because" + " few entity types miss Custom attribute configurations:\n{}" + ).format("\n".join(log_message_items))) + + self.show_interface(items, title, event) + def get_hier_values(self, session, hier_attrs, focus_entity_ids): joined_entity_ids = self.join_keys(focus_entity_ids) hier_attr_ids = self.join_keys( @@ -243,26 +321,28 @@ class PushFrameValuesToTaskAction(BaseAction): session, task_entities_by_parent_id, hier_values_by_entity_id, - task_attrs + task_attr_id_by_keys ): - task_attr_ids_by_key = { - attr["key"]: attr["id"] - for attr in task_attrs - } + missing_keys = set() total_parents = len(hier_values_by_entity_id) - idx = 1 + idx = 0 for parent_id, values in hier_values_by_entity_id.items(): - self.log.info(( - "[{}/{}] {} Processing values to children. Values: {}" - ).format(idx, total_parents, parent_id, values)) idx += 1 + self.log.info(( + "[{}/{}] {} Processing values to task. Values: {}" + ).format(idx, total_parents, parent_id, values)) task_entities = task_entities_by_parent_id[parent_id] - for key, value in values.items(): + for hier_key, value in values.items(): + key = self.custom_attribute_mapping[hier_key] + if key not in task_attr_id_by_keys: + missing_keys.add(key) + continue + for task_entity in task_entities: _entity_key = collections.OrderedDict({ - "configuration_id": task_attr_ids_by_key[key], + "configuration_id": task_attr_id_by_keys[key], "entity_id": task_entity["id"] }) @@ -277,6 +357,89 @@ class PushFrameValuesToTaskAction(BaseAction): ) session.commit() + return missing_keys + + def push_values_to_entities( + self, + session, + non_task_entities, + hier_values_by_entity_id + ): + object_types = session.query( + "ObjectType where name in ({})".format( + self.join_keys(self.pushing_entity_types) + ) + ).all() + object_type_names_by_id = { + object_type["id"]: object_type["name"] + for object_type in object_types + } + joined_keys = self.join_keys( + self.custom_attribute_mapping.values() + ) + attribute_entities = session.query( + self.cust_attrs_query.format(joined_keys) + ).all() + + attrs_by_obj_id = {} + for attr in attribute_entities: + if attr["is_hierarchical"]: + continue + + obj_id = attr["object_type_id"] + if obj_id not in object_type_names_by_id: + continue + + if obj_id not in attrs_by_obj_id: + attrs_by_obj_id[obj_id] = {} + + attr_key = attr["key"] + attrs_by_obj_id[obj_id][attr_key] = attr["id"] + + entities_by_obj_id = collections.defaultdict(list) + for entity in non_task_entities: + entities_by_obj_id[entity["object_type_id"]].append(entity) + + missing_keys_by_object_id = collections.defaultdict(set) + for obj_type_id, attr_keys in attrs_by_obj_id.items(): + entities = entities_by_obj_id.get(obj_type_id) + if not entities: + continue + + for entity in entities: + values = hier_values_by_entity_id.get(entity["id"]) + if not values: + continue + + for hier_key, value in values.items(): + key = self.custom_attribute_mapping[hier_key] + if key not in attr_keys: + missing_keys_by_object_id[obj_type_id].add(key) + continue + + _entity_key = collections.OrderedDict({ + "configuration_id": attr_keys[key], + "entity_id": entity["id"] + }) + + session.recorded_operations.push( + ftrack_api.operation.UpdateEntityOperation( + "ContextCustomAttributeValue", + _entity_key, + "value", + ftrack_api.symbol.NOT_SET, + value + ) + ) + session.commit() + + missing_keys_by_object_name = {} + for obj_id, missing_keys in missing_keys_by_object_id.items(): + obj_name = object_type_names_by_id[obj_id] + missing_keys_by_object_name[obj_name] = missing_keys + + return missing_keys_by_object_name + def register(session, plugins_presets={}): PushFrameValuesToTaskAction(session, plugins_presets).register() From 044434b35205f10e0d7d415d7d32ecefdbd65257 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 19:32:36 +0200 Subject: [PATCH 449/947] event handle the same way as action --- .../events/event_push_frame_values_to_task.py | 145 ++++++++++++++---- 1 file changed, 114 insertions(+), 31 deletions(-) diff --git a/pype/modules/ftrack/events/event_push_frame_values_to_task.py b/pype/modules/ftrack/events/event_push_frame_values_to_task.py index dd5c5911ec..dd24110c1b 100644 --- a/pype/modules/ftrack/events/event_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/event_push_frame_values_to_task.py @@ -4,18 +4,27 @@ from pype.modules.ftrack import BaseEvent class PushFrameValuesToTaskEvent(BaseEvent): - """Action for testing purpose or as base for new actions.""" - cust_attrs_query = ( - "select id, key, object_type_id, is_hierarchical, default" - " from CustomAttributeConfiguration" - " where key in ({}) and object_type_id = {}" - ) - # Ignore event handler by default ignore_me = True - interest_attributes = ["frameStart", "frameEnd"] + cust_attrs_query = ( + "select id, key, object_type_id, is_hierarchical, default" + " from CustomAttributeConfiguration" + " where key in ({}) and object_type_id in ({})" + ) + + interest_entity_types = {"Shot"} + interest_attributes = {"frameStart", "frameEnd"} + interest_attr_mapping = { + "frameStart": "fstart", + "frameEnd": "fend" + } _cached_task_object_id = None + _cached_interest_object_ids = None + + @staticmethod + def join_keys(keys): + return ",".join(["\"{}\"".format(key) for key in keys]) @classmethod def task_object_id(cls, session): @@ -26,6 +35,20 @@ class PushFrameValuesToTaskEvent(BaseEvent): cls._cached_task_object_id = task_object_type["id"] return cls._cached_task_object_id + @classmethod + def interest_object_ids(cls, session): + if cls._cached_interest_object_ids is None: + object_types = session.query( + "ObjectType where name in ({})".format( + cls.join_keys(cls.interest_entity_types) + ) + ).all() + cls._cached_interest_object_ids = tuple( + object_type["id"] + for object_type in object_types + ) + return cls._cached_interest_object_ids + def extract_interesting_data(self, session, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") @@ -60,60 +83,107 @@ class PushFrameValuesToTaskEvent(BaseEvent): interesting_data[entity_info["entityId"]] = entity_changes return interesting_data - def join_keys(self, keys): - return ",".join(["\"{}\"".format(key) for key in keys]) - - def get_task_entities(self, session, entities_info): - return session.query( - "Task where parent_id in ({})".format( - self.join_keys(entities_info.keys()) + def get_entities(self, session, interesting_data): + entities = session.query( + "TypedContext where id in ({})".format( + self.join_keys(interesting_data.keys()) ) ).all() - def task_attrs(self, session): - return session.query(self.cust_attrs_query.format( - self.join_keys(self.interest_attributes), - self.task_object_id(session) + output = [] + interest_object_ids = self.interest_object_ids(session) + for entity in entities: + if entity["object_type_id"] in interest_object_ids: + output.append(entity) + return output + + def get_task_entities(self, session, interesting_data): + return session.query( + "Task where parent_id in ({})".format( + self.join_keys(interesting_data.keys()) + ) + ).all() + + def attrs_configurations(self, session): + object_ids = list(self.interest_object_ids(session)) + object_ids.append(self.task_object_id(session)) + + attrs = session.query(self.cust_attrs_query.format( + self.join_keys(self.interest_attr_mapping.values()), + self.join_keys(object_ids) )).all() + output = {} + for attr in attrs: + obj_id = attr["object_type_id"] + if obj_id not in output: + output[obj_id] = {} + output[obj_id][attr["key"]] = attr["id"] + return output + def launch(self, session, event): interesting_data = self.extract_interesting_data(session, event) if not interesting_data: return + entities = self.get_entities(session, interesting_data) + if not entities: + return + + entities_by_id = { + entity["id"]: entity + for entity in entities + } + for entity_id in tuple(interesting_data.keys()): + if entity_id not in entities_by_id: + interesting_data.pop(entity_id) + task_entities = self.get_task_entities(session, interesting_data) if not task_entities: return - task_attrs = self.task_attrs(session) - if not task_attrs: + attrs_by_obj_id = self.attrs_configurations(session) + if not attrs_by_obj_id: self.log.warning(( "There is not created Custom Attributes {}" " for \"Task\" entity type." ).format(self.join_keys(self.interest_attributes))) return - task_attr_id_by_key = { - attr["key"]: attr["id"] - for attr in task_attrs - } task_entities_by_parent_id = collections.defaultdict(list) for task_entity in task_entities: task_entities_by_parent_id[task_entity["parent_id"]].append( task_entity ) + missing_keys_by_object_name = collections.defaultdict(set) for parent_id, values in interesting_data.items(): - task_entities = task_entities_by_parent_id[parent_id] + entities = task_entities_by_parent_id.get(parent_id) or [] + entities.append(entities_by_id[parent_id]) + for key, value in values.items(): changed_ids = [] - for task_entity in task_entities: - task_id = task_entity["id"] - changed_ids.append(task_id) + for entity in entities: + entity_attrs_mapping = ( + attrs_by_obj_id.get(entity["object_type_id"]) + ) + if not entity_attrs_mapping: + missing_keys_by_object_name[entity.entity_key].add( + key + ) + continue + configuration_id = entity_attrs_mapping.get(key) + if not configuration_id: + missing_keys_by_object_name[entity.entity_key].add( + key + ) + continue + + changed_ids.append(entity["id"]) entity_key = collections.OrderedDict({ - "configuration_id": task_attr_id_by_key[key], - "entity_id": task_id + "configuration_id": configuration_id, + "entity_id": entity["id"] }) if value is None: op = ftrack_api.operation.DeleteEntityOperation( @@ -142,6 +212,19 @@ class PushFrameValuesToTaskEvent(BaseEvent): "Changing of values failed.", exc_info=True ) + if not missing_keys_by_object_name: + return + + msg_items = [] + for object_name, missing_keys in missing_keys_by_object_name.items(): + msg_items.append( + "{}: ({})".format(object_name, self.join_keys(missing_keys)) + ) + + self.log.warning(( + "Missing Custom Attribute configuration" + " per specific object types: {}" + ).format(", ".join(msg_items))) def register(session, plugins_presets): From a628260c82d6c787960df501560fea8999acbc16 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 19:33:42 +0200 Subject: [PATCH 450/947] moved code in better order --- .../events/event_push_frame_values_to_task.py | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/pype/modules/ftrack/events/event_push_frame_values_to_task.py b/pype/modules/ftrack/events/event_push_frame_values_to_task.py index dd24110c1b..d4056c2ae5 100644 --- a/pype/modules/ftrack/events/event_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/event_push_frame_values_to_task.py @@ -49,78 +49,6 @@ class PushFrameValuesToTaskEvent(BaseEvent): ) return cls._cached_interest_object_ids - def extract_interesting_data(self, session, event): - # Filter if event contain relevant data - entities_info = event["data"].get("entities") - if not entities_info: - return - - interesting_data = {} - for entity_info in entities_info: - # Care only about tasks - if entity_info.get("entityType") != "task": - continue - - # Care only about changes of status - changes = entity_info.get("changes") or {} - if not changes: - continue - - # Care only about changes if specific keys - entity_changes = {} - for key in self.interest_attributes: - if key in changes: - entity_changes[key] = changes[key]["new"] - - if not entity_changes: - continue - - # Do not care about "Task" entity_type - task_object_id = self.task_object_id(session) - if entity_info.get("objectTypeId") == task_object_id: - continue - - interesting_data[entity_info["entityId"]] = entity_changes - return interesting_data - - def get_entities(self, session, interesting_data): - entities = session.query( - "TypedContext where id in ({})".format( - self.join_keys(interesting_data.keys()) - ) - ).all() - - output = [] - interest_object_ids = self.interest_object_ids(session) - for entity in entities: - if entity["object_type_id"] in interest_object_ids: - output.append(entity) - return output - - def get_task_entities(self, session, interesting_data): - return session.query( - "Task where parent_id in ({})".format( - self.join_keys(interesting_data.keys()) - ) - ).all() - - def attrs_configurations(self, session): - object_ids = list(self.interest_object_ids(session)) - object_ids.append(self.task_object_id(session)) - - attrs = session.query(self.cust_attrs_query.format( - self.join_keys(self.interest_attr_mapping.values()), - self.join_keys(object_ids) - )).all() - - output = {} - for attr in attrs: - obj_id = attr["object_type_id"] - if obj_id not in output: - output[obj_id] = {} - output[obj_id][attr["key"]] = attr["id"] - return output - def launch(self, session, event): interesting_data = self.extract_interesting_data(session, event) if not interesting_data: @@ -226,6 +154,78 @@ class PushFrameValuesToTaskEvent(BaseEvent): " per specific object types: {}" ).format(", ".join(msg_items))) + def extract_interesting_data(self, session, event): + # Filter if event contain relevant data + entities_info = event["data"].get("entities") + if not entities_info: + return + + interesting_data = {} + for entity_info in entities_info: + # Care only about tasks + if entity_info.get("entityType") != "task": + continue + + # Care only about changes of status + changes = entity_info.get("changes") or {} + if not changes: + continue + + # Care only about changes if specific keys + entity_changes = {} + for key in self.interest_attributes: + if key in changes: + entity_changes[key] = changes[key]["new"] + + if not entity_changes: + continue + + # Do not care about "Task" entity_type + task_object_id = self.task_object_id(session) + if entity_info.get("objectTypeId") == task_object_id: + continue + + interesting_data[entity_info["entityId"]] = entity_changes + return interesting_data + + def get_entities(self, session, interesting_data): + entities = session.query( + "TypedContext where id in ({})".format( + self.join_keys(interesting_data.keys()) + ) + ).all() + + output = [] + interest_object_ids = self.interest_object_ids(session) + for entity in entities: + if entity["object_type_id"] in interest_object_ids: + output.append(entity) + return output + + def get_task_entities(self, session, interesting_data): + return session.query( + "Task where parent_id in ({})".format( + self.join_keys(interesting_data.keys()) + ) + ).all() + + def attrs_configurations(self, session): + object_ids = list(self.interest_object_ids(session)) + object_ids.append(self.task_object_id(session)) + + attrs = session.query(self.cust_attrs_query.format( + self.join_keys(self.interest_attr_mapping.values()), + self.join_keys(object_ids) + )).all() + + output = {} + for attr in attrs: + obj_id = attr["object_type_id"] + if obj_id not in output: + output[obj_id] = {} + output[obj_id][attr["key"]] = attr["id"] + return output + def register(session, plugins_presets): PushFrameValuesToTaskEvent(session, plugins_presets).register() From 293ceb8e0bffda6dd4f1975633c915f5457ccb6f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 19:33:53 +0200 Subject: [PATCH 451/947] fixed few minor bugs --- .../ftrack/events/event_push_frame_values_to_task.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pype/modules/ftrack/events/event_push_frame_values_to_task.py b/pype/modules/ftrack/events/event_push_frame_values_to_task.py index d4056c2ae5..32993ef938 100644 --- a/pype/modules/ftrack/events/event_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/event_push_frame_values_to_task.py @@ -67,8 +67,6 @@ class PushFrameValuesToTaskEvent(BaseEvent): interesting_data.pop(entity_id) task_entities = self.get_task_entities(session, interesting_data) - if not task_entities: - return attrs_by_obj_id = self.attrs_configurations(session) if not attrs_by_obj_id: @@ -89,21 +87,22 @@ class PushFrameValuesToTaskEvent(BaseEvent): entities = task_entities_by_parent_id.get(parent_id) or [] entities.append(entities_by_id[parent_id]) - for key, value in values.items(): + for hier_key, value in values.items(): changed_ids = [] for entity in entities: + key = self.interest_attr_mapping[hier_key] entity_attrs_mapping = ( attrs_by_obj_id.get(entity["object_type_id"]) ) if not entity_attrs_mapping: - missing_keys_by_object_name[entity.entity_key].add( + missing_keys_by_object_name[entity.entity_type].add( key ) continue configuration_id = entity_attrs_mapping.get(key) if not configuration_id: - missing_keys_by_object_name[entity.entity_key].add( + missing_keys_by_object_name[entity.entity_type].add( key ) continue From 009a02f104019c64e94a7cacc4b7c8748c56d018 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 Aug 2020 19:39:49 +0200 Subject: [PATCH 452/947] removed unnecessary logs --- .../ftrack/events/action_push_frame_values_to_task.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index d88f4a1016..dec34a58cb 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -326,13 +326,7 @@ class PushFrameValuesToTaskAction(BaseAction): missing_keys = set() total_parents = len(hier_values_by_entity_id) - idx = 0 for parent_id, values in hier_values_by_entity_id.items(): - idx += 1 - self.log.info(( - "[{}/{}] {} Processing values to task. Values: {}" - ).format(idx, total_parents, parent_id, values)) - task_entities = task_entities_by_parent_id[parent_id] for hier_key, value in values.items(): key = self.custom_attribute_mapping[hier_key] From 576beb744687294bd12f48ce64d7a9ac8d1361bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 20 Aug 2020 20:34:50 +0200 Subject: [PATCH 453/947] removed unused variable --- pype/modules/ftrack/events/action_push_frame_values_to_task.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index dec34a58cb..a55c1e46a6 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -324,8 +324,6 @@ class PushFrameValuesToTaskAction(BaseAction): task_attr_id_by_keys ): missing_keys = set() - - total_parents = len(hier_values_by_entity_id) for parent_id, values in hier_values_by_entity_id.items(): task_entities = task_entities_by_parent_id[parent_id] for hier_key, value in values.items(): From 2a08d0e44f038a65debe9ec6a96a184c8bfb80d0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 11:13:59 +0200 Subject: [PATCH 454/947] taks are update not overriden during extract hierarchy plugin --- pype/plugins/global/publish/extract_hierarchy_avalon.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index ab8226f6ef..7cea825541 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -78,6 +78,12 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} + new_tasks = data.pop("tasks", []) + if "tasks" in cur_entity_data and new_tasks: + for task_name in new_tasks: + if task_name not in cur_entity_data["tasks"]: + cur_entity_data["tasks"].append(task_name) + cur_entity_data.update(data) data = cur_entity_data else: From 693d8e75dc37a5a3f072dcee4fe1494a2448b12f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 11:50:01 +0200 Subject: [PATCH 455/947] use default icon if icon for variants was not found --- pype/tools/launcher/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/tools/launcher/models.py b/pype/tools/launcher/models.py index 3fb201702e..b2743d221c 100644 --- a/pype/tools/launcher/models.py +++ b/pype/tools/launcher/models.py @@ -199,6 +199,9 @@ class ActionModel(QtGui.QStandardItemModel): if order is None or action.order < order: order = action.order + if icon is None: + icon = self.default_icon + item = QtGui.QStandardItem(icon, action.label) item.setData(actions, self.ACTION_ROLE) item.setData(True, self.VARIANT_GROUP_ROLE) From b855038889b8b7ccb7e0e3cc849f7f896297c5a5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 12:56:14 +0200 Subject: [PATCH 456/947] change project overides that are only one file --- .../kuba_each_case/global/creator.json | 8 -------- .../kuba_each_case/global/intents.json | 3 --- .../kuba_each_case/plugins/maya/publish.json | 8 -------- .../kuba_each_case/project_presets.json | 18 ++++++++++++++++++ 4 files changed, 18 insertions(+), 19 deletions(-) delete mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json delete mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json delete mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json create mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json deleted file mode 100644 index d14e779f01..0000000000 --- a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Model": ["model"], - "Render Globals": ["light", "render"], - "Layout": ["layout"], - "Set Dress": ["setdress"], - "Look": ["look"], - "Rig": ["rigging"] -} diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json deleted file mode 100644 index bf147c7a19..0000000000 --- a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "default": "test" -} diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json deleted file mode 100644 index 46fc343b6c..0000000000 --- a/pype/tools/config_setting/config/project_overrides/kuba_each_case/plugins/maya/publish.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ValidateModelName": { - "enabled": true - }, - "ValidateAssemblyName": { - "enabled": false - } -} diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json new file mode 100644 index 0000000000..599a5dcbea --- /dev/null +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json @@ -0,0 +1,18 @@ +{ + "__override_version__": 1, + "plugins": { + "maya": { + "__overriden_keys__": [ + "publish" + ], + "publish": { + "ValidateModelName": { + "enabled": true + }, + "ValidateAssemblyName": { + "enabled": false + } + } + } + } +} From d01d9d55da1d5e1715cdf3a25a360114c71308ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 12:56:35 +0200 Subject: [PATCH 457/947] config can work with single file overrides --- pype/tools/config_setting/widgets/config.py | 34 +++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 8d9d4fa1d2..321a97a4b8 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -18,8 +18,9 @@ config_path = os.path.dirname(os.path.dirname(__file__)) studio_presets_path = os.path.normpath( os.path.join(config_path, "config", "studio_presets") ) +project_configurations_dir = "project_presets" project_presets_path = os.path.normpath( - os.path.join(config_path, "config", "project_presets") + os.path.join(config_path, "config", project_configurations_dir) ) first_run = False @@ -118,15 +119,18 @@ def load_jsons_from_dir(path, *args, **kwargs): base_len = len(path) + 1 for base, _directories, filenames in os.walk(path): + base_items_str = base[base_len:] + if not base_items_str: + base_items = [] + else: + base_items = base_items_str.split(os.path.sep) + for filename in filenames: basename, ext = os.path.splitext(filename) if ext == ".json": full_path = os.path.join(base, filename) value = load_json(full_path) - - # dict_path = os.path.join(base[base_len:], basename) - # dict_keys = dict_path.split(os.path.sep) - dict_keys = base[base_len:].split(os.path.sep) + [basename] + dict_keys = base_items + [basename] output = subkey_merge(output, value, dict_keys) for sub_key in sub_keys: @@ -145,27 +149,31 @@ def global_project_presets(**kwargs): def project_preset_overrides(project_name, **kwargs): project_configs_path = os.environ.get("PYPE_PROJECT_CONFIGS") if project_name and project_configs_path: - return load_jsons_from_dir( + result = load_jsons_from_dir( os.path.join(project_configs_path, project_name), **kwargs ) + print(json.dumps(result, indent=4)) + if result: + result = result.get(project_configurations_dir) or {} + return result return {} def merge_overrides(global_dict, override_dict): if OVERRIDEN_KEY in override_dict: - _override = override_dict.pop(OVERRIDEN_KEY) - if _override: - return override_dict + overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) + else: + overriden_keys = set() for key, value in override_dict.items(): if value == POP_KEY: global_dict.pop(key) - elif key == OVERRIDEN_KEY: - continue - - elif key not in global_dict: + elif ( + key in overriden_keys + or key not in global_dict + ): global_dict[key] = value elif isinstance(value, dict) and isinstance(global_dict[key], dict): From 742c2b069b28203ec1703f36109d5497f00e1690 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 13:12:04 +0200 Subject: [PATCH 458/947] removed debug print --- pype/tools/config_setting/widgets/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/config_setting/widgets/lib.py b/pype/tools/config_setting/widgets/lib.py index fd3f45b590..d733396d59 100644 --- a/pype/tools/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/widgets/lib.py @@ -28,7 +28,6 @@ def convert_gui_data_to_overrides(data, first=True): metadata = data.pop(METADATA_KEY) for key, value in metadata.items(): if key == "groups": - print("**", value) output[OVERRIDEN_KEY] = value else: KeyError("Unknown metadata key \"{}\"".format(key)) From d830ad8b00811bea3fd2937d7c82a8f9b03db5fe Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 21 Aug 2020 15:13:36 +0200 Subject: [PATCH 459/947] deal with tasks in edit --- pype/plugins/global/publish/extract_hierarchy_avalon.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index ab8226f6ef..1d8191f2e3 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -78,6 +78,11 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} + new_tasks = data.pop("tasks", []) + if "tasks" in cur_entity_data and new_tasks: + for task_name in new_tasks: + if task_name not in cur_entity_data["tasks"]: + cur_entity_data["tasks"].append(task_name) cur_entity_data.update(data) data = cur_entity_data else: From 7584d1ef72e0a03364ff112ec1475be6825cfdbd Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 21 Aug 2020 15:29:49 +0200 Subject: [PATCH 460/947] load audio and refence in harmony --- pype/plugins/harmony/load/load_audio.py | 2 +- pype/plugins/harmony/load/load_imagesequence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/harmony/load/load_audio.py b/pype/plugins/harmony/load/load_audio.py index 694fda3247..600791e61a 100644 --- a/pype/plugins/harmony/load/load_audio.py +++ b/pype/plugins/harmony/load/load_audio.py @@ -31,7 +31,7 @@ func class ImportAudioLoader(api.Loader): """Import audio.""" - families = ["shot"] + families = ["shot", "audio"] representations = ["wav"] label = "Import Audio" diff --git a/pype/plugins/harmony/load/load_imagesequence.py b/pype/plugins/harmony/load/load_imagesequence.py index 774782b092..c5f50a7d23 100644 --- a/pype/plugins/harmony/load/load_imagesequence.py +++ b/pype/plugins/harmony/load/load_imagesequence.py @@ -230,7 +230,7 @@ class ImageSequenceLoader(api.Loader): """Load images Stores the imported asset in a container named after the asset. """ - families = ["shot", "render", "image", "plate"] + families = ["shot", "render", "image", "plate", "reference"] representations = ["jpeg", "png", "jpg"] def load(self, context, name=None, namespace=None, data=None): From f8492befdfe8b4a0c5f73139f61a046ec1fac645 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 15:38:09 +0200 Subject: [PATCH 461/947] fixed getting overrides --- pype/tools/config_setting/widgets/config.py | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 321a97a4b8..7bce18b7f4 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -18,9 +18,9 @@ config_path = os.path.dirname(os.path.dirname(__file__)) studio_presets_path = os.path.normpath( os.path.join(config_path, "config", "studio_presets") ) -project_configurations_dir = "project_presets" +PROJECT_CONFIGURATION_DIR = "project_presets" project_presets_path = os.path.normpath( - os.path.join(config_path, "config", project_configurations_dir) + os.path.join(config_path, "config", PROJECT_CONFIGURATION_DIR) ) first_run = False @@ -146,18 +146,20 @@ def global_project_presets(**kwargs): return load_jsons_from_dir(project_presets_path, **kwargs) +def path_to_project_overrides(project_name): + project_configs_path = os.environ["PYPE_PROJECT_CONFIGS"] + dirpath = os.path.join(project_configs_path, project_name) + return os.path.join(dirpath, PROJECT_CONFIGURATION_DIR + ".json") + + def project_preset_overrides(project_name, **kwargs): - project_configs_path = os.environ.get("PYPE_PROJECT_CONFIGS") - if project_name and project_configs_path: - result = load_jsons_from_dir( - os.path.join(project_configs_path, project_name), - **kwargs - ) - print(json.dumps(result, indent=4)) - if result: - result = result.get(project_configurations_dir) or {} - return result - return {} + if not project_name: + return {} + + path_to_json = path_to_project_overrides(project_name) + if not os.path.exists(path_to_json): + return {} + return load_json(path_to_json) def merge_overrides(global_dict, override_dict): @@ -227,7 +229,7 @@ def replace_inner_schemas(schema_data, schema_collection): return schema_data -class ShemaMissingFileInfo(Exception): +class SchemaMissingFileInfo(Exception): def __init__(self, invalid): full_path_keys = [] for item in invalid: @@ -237,7 +239,7 @@ class ShemaMissingFileInfo(Exception): "Schema has missing definition of output file (\"is_file\" key)" " for keys. [{}]" ).format(", ".join(full_path_keys)) - super(ShemaMissingFileInfo, self).__init__(msg) + super(SchemaMissingFileInfo, self).__init__(msg) def file_keys_from_schema(schema_data): @@ -290,7 +292,7 @@ def validate_all_has_ending_file(schema_data, is_top=True): if not is_top: return invalid - raise ShemaMissingFileInfo(invalid) + raise SchemaMissingFileInfo(invalid) def validate_schema(schema_data): From 1043b70550ac42409329dc6e6b43fce8d2517028 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 15:38:26 +0200 Subject: [PATCH 462/947] added override saving --- .../projects_schema/project_gui_schema.json | 3 +-- pype/tools/config_setting/widgets/base.py | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json b/pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json index 366400e5ff..0405524b40 100644 --- a/pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json +++ b/pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json @@ -1,7 +1,6 @@ { - "key": "studio", + "key": "project", "type": "dict-invisible", - "label": "Studio", "children": [ { "type": "schema", diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index bdacb60559..259ec9e02c 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -74,8 +74,6 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): content_layout.setAlignment(QtCore.Qt.AlignTop) content_widget.setLayout(content_layout) - # scroll_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - # scroll_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) scroll_widget.setWidgetResizable(True) scroll_widget.setWidget(content_widget) @@ -404,8 +402,17 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): if groups: data[METADATA_KEY] = {"groups": groups} - output = convert_gui_data_to_overrides(data) - print(json.dumps(output, indent=4)) + output_data = convert_gui_data_to_overrides(data) + + overrides_json_path = config.path_to_project_overrides( + self.project_name + ) + dirpath = os.path.dirname(overrides_json_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(overrides_json_path, "w") as file_stream: + json.dump(output_data, file_stream, indent=4) def _save_defaults(self): output = {} @@ -429,7 +436,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): all_values = _all_values # Skip first key - all_values = all_values["studio"] + all_values = all_values["project"] # Load studio data with metadata current_presets = config.studio_presets() From 4ce1f70f88909ff0d60dbb6f45d42580c0428d88 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 15:57:50 +0200 Subject: [PATCH 463/947] saving works properly --- .../kuba_each_case/project_presets.json | 2 +- pype/tools/config_setting/widgets/base.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json index 599a5dcbea..b9da242453 100644 --- a/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json @@ -15,4 +15,4 @@ } } } -} +} \ No newline at end of file diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 259ec9e02c..2421c02a25 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -390,18 +390,17 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): self._save_overrides() def _save_overrides(self): - data = {} - groups = [] + _data = {} for item in self.input_fields: value, is_group = item.overrides() if value is not NOT_SET: - data.update(value) - + _data.update(value) if is_group: - groups.extend(value.keys()) + raise Exception( + "Top item can't be overriden in Project widget." + ) - if groups: - data[METADATA_KEY] = {"groups": groups} + data = _data.get("project") or {} output_data = convert_gui_data_to_overrides(data) overrides_json_path = config.path_to_project_overrides( From 993b0ab21fdc60461176894f461f936721231152 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 16:03:27 +0200 Subject: [PATCH 464/947] default project configurations works --- pype/tools/config_setting/widgets/base.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 2421c02a25..0563f942be 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -357,6 +357,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): schema = config.gui_schema("projects_schema", "project_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) + self.schema = schema def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] @@ -422,10 +423,6 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): _output = {key: output} output = _output - print(json.dumps(output, indent=4)) - return - - # TODO check implementation copied from studio all_values = {} for item in self.input_fields: all_values.update(item.config_value()) @@ -438,7 +435,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): all_values = all_values["project"] # Load studio data with metadata - current_presets = config.studio_presets() + current_presets = config.global_project_presets() keys_to_file = config.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: @@ -458,7 +455,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): origin_values.update(new_values) output_path = os.path.join( - config.studio_presets_path, subpath + config.project_presets_path, subpath ) dirpath = os.path.dirname(output_path) if not os.path.exists(dirpath): From 1c47579334406a1b650c05d49b3dcf99ae8e0023 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 16:08:55 +0200 Subject: [PATCH 465/947] modified getting paths --- pype/tools/config_setting/widgets/config.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index 7bce18b7f4..b071d81afe 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -4,29 +4,25 @@ import logging import copy # DEBUG SETUP -os.environ["AVALON_PROJECT"] = "kuba_each_case" +os.environ["PYPE_CONFIG"] = os.path.dirname(os.path.dirname(__file__)) os.environ["PYPE_PROJECT_CONFIGS"] = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - "config", - "project_overrides" + os.environ["PYPE_CONFIG"], "config", "project_overrides" ) -# log = logging.getLogger(__name__) -config_path = os.path.dirname(os.path.dirname(__file__)) studio_presets_path = os.path.normpath( - os.path.join(config_path, "config", "studio_presets") + os.path.join(os.environ["PYPE_CONFIG"], "config", "studio_presets") ) PROJECT_CONFIGURATION_DIR = "project_presets" project_presets_path = os.path.normpath( - os.path.join(config_path, "config", PROJECT_CONFIGURATION_DIR) + os.path.join(os.environ["PYPE_CONFIG"], "config", PROJECT_CONFIGURATION_DIR) ) first_run = False -OVERRIDEN_KEY = "__overriden_keys__" # TODO key popping not implemented yet POP_KEY = "__pop_key__" +OVERRIDEN_KEY = "__overriden_keys__" def load_json(fpath): From 57cac7135ddb644ba6e3ec9a08efb0d34452febb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 16:10:24 +0200 Subject: [PATCH 466/947] changed global variables --- pype/tools/config_setting/widgets/base.py | 4 ++-- pype/tools/config_setting/widgets/config.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py index 0563f942be..0fc9c5cabf 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/widgets/base.py @@ -147,7 +147,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): origin_values.update(new_values) output_path = os.path.join( - config.studio_presets_path, subpath + config.STUDIO_PRESETS_PATH, subpath ) dirpath = os.path.dirname(output_path) if not os.path.exists(dirpath): @@ -455,7 +455,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): origin_values.update(new_values) output_path = os.path.join( - config.project_presets_path, subpath + config.PROJECT_PRESETS_PATH, subpath ) dirpath = os.path.dirname(output_path) if not os.path.exists(dirpath): diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py index b071d81afe..34eec69fc7 100644 --- a/pype/tools/config_setting/widgets/config.py +++ b/pype/tools/config_setting/widgets/config.py @@ -11,13 +11,13 @@ os.environ["PYPE_PROJECT_CONFIGS"] = os.path.join( log = logging.getLogger(__name__) -studio_presets_path = os.path.normpath( +STUDIO_PRESETS_PATH = os.path.normpath( os.path.join(os.environ["PYPE_CONFIG"], "config", "studio_presets") ) PROJECT_CONFIGURATION_DIR = "project_presets" -project_presets_path = os.path.normpath( - os.path.join(os.environ["PYPE_CONFIG"], "config", PROJECT_CONFIGURATION_DIR) -) +PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( + os.environ["PYPE_CONFIG"], "config", PROJECT_CONFIGURATION_DIR +)) first_run = False # TODO key popping not implemented yet @@ -135,11 +135,11 @@ def load_jsons_from_dir(path, *args, **kwargs): def studio_presets(*args, **kwargs): - return load_jsons_from_dir(studio_presets_path, *args, **kwargs) + return load_jsons_from_dir(STUDIO_PRESETS_PATH, *args, **kwargs) def global_project_presets(**kwargs): - return load_jsons_from_dir(project_presets_path, **kwargs) + return load_jsons_from_dir(PROJECT_PRESETS_PATH, **kwargs) def path_to_project_overrides(project_name): From ad7b7c6e220ccb0993e46f3e9036ee97ac531010 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Aug 2020 16:19:59 +0200 Subject: [PATCH 467/947] fix(global): not default True value --- pype/plugins/global/publish/extract_jpeg.py | 2 +- pype/plugins/global/publish/extract_review.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index dd5f79b6ac..f6f41d1397 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -27,7 +27,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): return # Skip review when requested. - if not instance.data.get("review"): + if not instance.data.get("review", True): return # get representation and loop them diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 3a5bd3464a..0bae1b2ddc 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -51,7 +51,7 @@ class ExtractReview(pyblish.api.InstancePlugin): def process(self, instance): # Skip review when requested. - if not instance.data.get("review"): + if not instance.data.get("review", True): return # ffmpeg doesn't support multipart exrs From 0d711c3b7f8c28cd92a3674eeff880766c7f8054 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 21 Aug 2020 16:25:48 +0100 Subject: [PATCH 468/947] Get linked assets from "inputs". --- pype/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index 7cf4e2f1a5..601c85f521 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -746,8 +746,9 @@ class PypeHook: def get_linked_assets(asset_entity): """Return linked assets for `asset_entity`.""" - # TODO implement - return [] + inputs = asset_entity["data"].get("inputs", []) + inputs = [io.find_one({"_id": x}) for x in inputs] + return inputs def map_subsets_by_family(subsets): From 1ac12eadb2d3e2a4c0530e830e195c30e4cf9970 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 17:38:03 +0200 Subject: [PATCH 469/947] reorganized config setting tool to usable as tool --- pype/tools/config_setting/__init__.py | 7 + pype/tools/config_setting/__main__.py | 18 + .../config_setting/config_setting/__init__.py | 10 + .../ftrack_projects_gui_schema.json | 0 .../projects_schema/plugins_gui_schema.json | 0 .../projects_schema/project_gui_schema.json | 0 .../projects_schema/test_project_schema.json | 0 .../applications_gui_schema.json | 0 .../studio_schema/studio_gui_schema.json | 0 .../studio_schema/tools_gui_schema.json | 0 .../{ => config_setting}/style/__init__.py | 0 .../{ => config_setting}/style/pype_icon.png | Bin .../{ => config_setting}/style/style.css | 0 .../config_setting/widgets/__init__.py | 19 + .../{ => config_setting}/widgets/base.py | 28 +- .../{ => config_setting}/widgets/inputs.py | 6 +- .../config_setting/widgets/lib.py | 182 ++++++++++ .../{ => config_setting}/widgets/main.py | 0 .../{ => config_setting}/widgets/tests.py | 0 .../{ => config_setting}/widgets/widgets.py | 0 pype/tools/config_setting/interface.py | 56 --- pype/tools/config_setting/widgets/__init__.py | 6 - pype/tools/config_setting/widgets/config.py | 325 ------------------ pype/tools/config_setting/widgets/lib.py | 54 --- 24 files changed, 251 insertions(+), 460 deletions(-) create mode 100644 pype/tools/config_setting/__init__.py create mode 100644 pype/tools/config_setting/__main__.py create mode 100644 pype/tools/config_setting/config_setting/__init__.py rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/projects_schema/plugins_gui_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/projects_schema/project_gui_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/projects_schema/test_project_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/studio_schema/applications_gui_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/studio_schema/studio_gui_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/config_gui_schema/studio_schema/tools_gui_schema.json (100%) rename pype/tools/config_setting/{ => config_setting}/style/__init__.py (100%) rename pype/tools/config_setting/{ => config_setting}/style/pype_icon.png (100%) rename pype/tools/config_setting/{ => config_setting}/style/style.css (100%) create mode 100644 pype/tools/config_setting/config_setting/widgets/__init__.py rename pype/tools/config_setting/{ => config_setting}/widgets/base.py (95%) rename pype/tools/config_setting/{ => config_setting}/widgets/inputs.py (99%) create mode 100644 pype/tools/config_setting/config_setting/widgets/lib.py rename pype/tools/config_setting/{ => config_setting}/widgets/main.py (100%) rename pype/tools/config_setting/{ => config_setting}/widgets/tests.py (100%) rename pype/tools/config_setting/{ => config_setting}/widgets/widgets.py (100%) delete mode 100644 pype/tools/config_setting/interface.py delete mode 100644 pype/tools/config_setting/widgets/__init__.py delete mode 100644 pype/tools/config_setting/widgets/config.py delete mode 100644 pype/tools/config_setting/widgets/lib.py diff --git a/pype/tools/config_setting/__init__.py b/pype/tools/config_setting/__init__.py new file mode 100644 index 0000000000..c3bd49449d --- /dev/null +++ b/pype/tools/config_setting/__init__.py @@ -0,0 +1,7 @@ +from config_setting import style, MainWidget + + +__all__ = ( + "style", + "MainWidget" +) diff --git a/pype/tools/config_setting/__main__.py b/pype/tools/config_setting/__main__.py new file mode 100644 index 0000000000..171b85a775 --- /dev/null +++ b/pype/tools/config_setting/__main__.py @@ -0,0 +1,18 @@ +import os +import sys + +import config_setting +from Qt import QtWidgets, QtGui + + +if __name__ == "__main__": + app = QtWidgets.QApplication(sys.argv) + + stylesheet = config_setting.style.load_stylesheet() + app.setStyleSheet(stylesheet) + app.setWindowIcon(QtGui.QIcon(config_setting.style.app_icon_path())) + + widget = config_setting.MainWidget() + widget.show() + + sys.exit(app.exec_()) diff --git a/pype/tools/config_setting/config_setting/__init__.py b/pype/tools/config_setting/config_setting/__init__.py new file mode 100644 index 0000000000..835754e6a1 --- /dev/null +++ b/pype/tools/config_setting/config_setting/__init__.py @@ -0,0 +1,10 @@ +from . import style +# from . import widgets +from .widgets import MainWidget + + +__all__ = ( + "style", + # "widgets", + "MainWidget" +) diff --git a/pype/tools/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/project_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/projects_schema/project_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/project_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/projects_schema/test_project_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/test_project_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/projects_schema/test_project_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/test_project_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json diff --git a/pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json diff --git a/pype/tools/config_setting/style/__init__.py b/pype/tools/config_setting/config_setting/style/__init__.py similarity index 100% rename from pype/tools/config_setting/style/__init__.py rename to pype/tools/config_setting/config_setting/style/__init__.py diff --git a/pype/tools/config_setting/style/pype_icon.png b/pype/tools/config_setting/config_setting/style/pype_icon.png similarity index 100% rename from pype/tools/config_setting/style/pype_icon.png rename to pype/tools/config_setting/config_setting/style/pype_icon.png diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css similarity index 100% rename from pype/tools/config_setting/style/style.css rename to pype/tools/config_setting/config_setting/style/style.css diff --git a/pype/tools/config_setting/config_setting/widgets/__init__.py b/pype/tools/config_setting/config_setting/widgets/__init__.py new file mode 100644 index 0000000000..0197917596 --- /dev/null +++ b/pype/tools/config_setting/config_setting/widgets/__init__.py @@ -0,0 +1,19 @@ +from .lib import ( + NOT_SET, + AS_WIDGET, + METADATA_KEY, + OVERRIDE_VERSION, + convert_gui_data_to_overrides, + convert_overrides_to_gui_data, + TypeToKlass +) + + +from .base import ( + PypeConfigurationWidget, + StudioWidget, + ProjectWidget +) +from .main import MainWidget + +from .inputs import * diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py similarity index 95% rename from pype/tools/config_setting/widgets/base.py rename to pype/tools/config_setting/config_setting/widgets/base.py index 0fc9c5cabf..dcbaf743a9 100644 --- a/pype/tools/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -1,16 +1,12 @@ import os import json from Qt import QtWidgets, QtCore, QtGui -from . import config +from pype.api import config from .widgets import UnsavedChangesDialog -from .lib import NOT_SET, METADATA_KEY, convert_gui_data_to_overrides +from . import lib from avalon import io -class TypeToKlass: - types = {} - - class PypeConfigurationWidget: default_state = "" @@ -23,7 +19,7 @@ class PypeConfigurationWidget: def value_from_values(self, values, keys=None): if not values: - return NOT_SET + return lib.NOT_SET if keys is None: keys = self.keys @@ -36,7 +32,7 @@ class PypeConfigurationWidget: ) if key not in value: - return NOT_SET + return lib.NOT_SET value = value[key] return value @@ -109,7 +105,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.input_fields.clear() values = {"studio": config.studio_presets()} - schema = config.gui_schema("studio_schema", "studio_gui_schema") + schema = lib.gui_schema("studio_schema", "studio_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) self.schema = schema @@ -129,7 +125,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): # Load studio data with metadata current_presets = config.studio_presets() - keys_to_file = config.file_keys_from_schema(self.schema) + keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: # Skip first key key_sequence = key_sequence[1:] @@ -158,7 +154,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) + klass = lib.TypeToKlass.types.get(item_type) item = klass( child_configuration, values, self.keys, self ) @@ -354,14 +350,14 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): def reset(self): values = config.global_project_presets() - schema = config.gui_schema("projects_schema", "project_gui_schema") + schema = lib.gui_schema("projects_schema", "project_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) self.schema = schema def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) + klass = lib.TypeToKlass.types.get(item_type) item = klass( child_configuration, values, self.keys, self @@ -394,7 +390,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): _data = {} for item in self.input_fields: value, is_group = item.overrides() - if value is not NOT_SET: + if value is not lib.NOT_SET: _data.update(value) if is_group: raise Exception( @@ -402,7 +398,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): ) data = _data.get("project") or {} - output_data = convert_gui_data_to_overrides(data) + output_data = lib.convert_gui_data_to_overrides(data) overrides_json_path = config.path_to_project_overrides( self.project_name @@ -437,7 +433,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): # Load studio data with metadata current_presets = config.global_project_presets() - keys_to_file = config.file_keys_from_schema(self.schema) + keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: # Skip first key key_sequence = key_sequence[1:] diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py similarity index 99% rename from pype/tools/config_setting/widgets/inputs.py rename to pype/tools/config_setting/config_setting/widgets/inputs.py index 1840572cfb..63415a16d3 100644 --- a/pype/tools/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1,14 +1,14 @@ import json from Qt import QtWidgets, QtCore, QtGui -from . import config -from .base import PypeConfigurationWidget, TypeToKlass +from pype.api import config +from .base import PypeConfigurationWidget from .widgets import ( ClickableWidget, ExpandingWidget, ModifiedIntSpinBox, ModifiedFloatSpinBox ) -from .lib import NOT_SET, AS_WIDGET, METADATA_KEY +from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass class SchemeGroupHierarchyBug(Exception): diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py new file mode 100644 index 0000000000..454c0b07ed --- /dev/null +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -0,0 +1,182 @@ +import os +import json +import copy +from pype.api import config + +OVERRIDEN_KEY = config.OVERRIDEN_KEY + + +# Singleton database of available inputs +class TypeToKlass: + types = {} + + +NOT_SET = type("NOT_SET", (), {}) +AS_WIDGET = type("AS_WIDGET", (), {}) +METADATA_KEY = type("METADATA_KEY", (), {}) +OVERRIDE_VERSION = 1 + + +def convert_gui_data_to_overrides(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if first: + output["__override_version__"] = OVERRIDE_VERSION + + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key == "groups": + output[OVERRIDEN_KEY] = value + else: + KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_to_overrides(value, False) + return output + + +def convert_overrides_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if OVERRIDEN_KEY in data: + groups = data.pop(OVERRIDEN_KEY) + if METADATA_KEY not in output: + output[METADATA_KEY] = {} + output[METADATA_KEY]["groups"] = groups + + for key, value in data.items(): + output[key] = convert_overrides_to_gui_data(value, False) + + return output + + + +def replace_inner_schemas(schema_data, schema_collection): + if schema_data["type"] == "schema": + raise ValueError("First item in schema data can't be schema.") + + children = schema_data.get("children") + if not children: + return schema_data + + new_children = [] + for child in children: + if child["type"] != "schema": + new_child = replace_inner_schemas(child, schema_collection) + new_children.append(new_child) + continue + + for schema_name in child["children"]: + new_child = replace_inner_schemas( + schema_collection[schema_name], + schema_collection + ) + new_children.append(new_child) + + schema_data["children"] = new_children + return schema_data + + +class SchemaMissingFileInfo(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Schema has missing definition of output file (\"is_file\" key)" + " for keys. [{}]" + ).format(", ".join(full_path_keys)) + super(SchemaMissingFileInfo, self).__init__(msg) + + +def file_keys_from_schema(schema_data): + output = [] + keys = [] + key = schema_data.get("key") + if key: + keys.append(key) + + for child in schema_data["children"]: + if child.get("is_file"): + _keys = copy.deepcopy(keys) + _keys.append(child["key"]) + output.append(_keys) + continue + + for result in file_keys_from_schema(child): + _keys = copy.deepcopy(keys) + _keys.extend(result) + output.append(_keys) + return output + + +def validate_all_has_ending_file(schema_data, is_top=True): + if schema_data.get("is_file"): + return None + + children = schema_data.get("children") + if not children: + return [[schema_data["key"]]] + + invalid = [] + keyless = "key" not in schema_data + for child in children: + result = validate_all_has_ending_file(child, False) + if result is None: + continue + + if keyless: + invalid.extend(result) + else: + for item in result: + new_invalid = [schema_data["key"]] + new_invalid.extend(item) + invalid.append(new_invalid) + + if not invalid: + return None + + if not is_top: + return invalid + + raise SchemaMissingFileInfo(invalid) + + +def validate_schema(schema_data): + # TODO validator for key uniquenes + # TODO validator that is_group key is not before is_file child + # TODO validator that is_group or is_file is not on child without key + validate_all_has_ending_file(schema_data) + + +def gui_schema(subfolder, main_schema_name): + subfolder, main_schema_name + dirpath = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema", + subfolder + ) + + loaded_schemas = {} + for filename in os.listdir(dirpath): + basename, ext = os.path.splitext(filename) + if ext != ".json": + continue + + filepath = os.path.join(dirpath, filename) + with open(filepath, "r") as json_stream: + schema_data = json.load(json_stream) + loaded_schemas[basename] = schema_data + + main_schema = replace_inner_schemas( + loaded_schemas[main_schema_name], + loaded_schemas + ) + validate_schema(main_schema) + return main_schema diff --git a/pype/tools/config_setting/widgets/main.py b/pype/tools/config_setting/config_setting/widgets/main.py similarity index 100% rename from pype/tools/config_setting/widgets/main.py rename to pype/tools/config_setting/config_setting/widgets/main.py diff --git a/pype/tools/config_setting/widgets/tests.py b/pype/tools/config_setting/config_setting/widgets/tests.py similarity index 100% rename from pype/tools/config_setting/widgets/tests.py rename to pype/tools/config_setting/config_setting/widgets/tests.py diff --git a/pype/tools/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py similarity index 100% rename from pype/tools/config_setting/widgets/widgets.py rename to pype/tools/config_setting/config_setting/widgets/widgets.py diff --git a/pype/tools/config_setting/interface.py b/pype/tools/config_setting/interface.py deleted file mode 100644 index a8c05f5af3..0000000000 --- a/pype/tools/config_setting/interface.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -import sys - - -def folder_up(path, times=1): - if times <= 0: - return path - return folder_up(os.path.dirname(path), times - 1) - - -PYPE_SETUP_PATH = folder_up(os.path.realpath(__file__), 6) -os.environ["PYPE_CONFIG"] = os.path.join( - PYPE_SETUP_PATH, "repos", "pype-config" -) -os.environ["AVALON_MONGO"] = "mongodb://localhost:2707" -sys_paths = ( - "C:/Users/Public/pype_env2/Lib/site-packages", - PYPE_SETUP_PATH, - os.path.join(PYPE_SETUP_PATH, "repos", "pype"), - os.path.join(PYPE_SETUP_PATH, "repos", "avalon-core"), - os.path.join(PYPE_SETUP_PATH, "repos", "pyblish-base") -) -for path in sys_paths: - sys.path.append(path) - -from widgets import main -import style -from Qt import QtWidgets, QtGui - - -class MyApp(QtWidgets.QApplication): - def __init__(self, *args, **kwargs): - super(MyApp, self).__init__(*args, **kwargs) - stylesheet = style.load_stylesheet() - self.setStyleSheet(stylesheet) - self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) - - -if __name__ == "__main__": - app = MyApp(sys.argv) - - # main_widget = QtWidgets.QWidget() - # main_widget.setWindowIcon(QtGui.QIcon(style.app_icon_path())) - # - # layout = QtWidgets.QVBoxLayout(main_widget) - # - # widget = main.MainWidget(main_widget) - - # layout.addWidget(widget) - # main_widget.setLayout(layout) - # main_widget.show() - - widget = main.MainWidget() - widget.show() - - sys.exit(app.exec_()) diff --git a/pype/tools/config_setting/widgets/__init__.py b/pype/tools/config_setting/widgets/__init__.py deleted file mode 100644 index 9fbce6e1cf..0000000000 --- a/pype/tools/config_setting/widgets/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .lib import NOT_SET, AS_WIDGET, METADATA_KEY - - -from .base import * -from .main import * -from .inputs import * diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py deleted file mode 100644 index 34eec69fc7..0000000000 --- a/pype/tools/config_setting/widgets/config.py +++ /dev/null @@ -1,325 +0,0 @@ -import os -import json -import logging -import copy - -# DEBUG SETUP -os.environ["PYPE_CONFIG"] = os.path.dirname(os.path.dirname(__file__)) -os.environ["PYPE_PROJECT_CONFIGS"] = os.path.join( - os.environ["PYPE_CONFIG"], "config", "project_overrides" -) - -log = logging.getLogger(__name__) - -STUDIO_PRESETS_PATH = os.path.normpath( - os.path.join(os.environ["PYPE_CONFIG"], "config", "studio_presets") -) -PROJECT_CONFIGURATION_DIR = "project_presets" -PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( - os.environ["PYPE_CONFIG"], "config", PROJECT_CONFIGURATION_DIR -)) -first_run = False - -# TODO key popping not implemented yet -POP_KEY = "__pop_key__" -OVERRIDEN_KEY = "__overriden_keys__" - - -def load_json(fpath): - # Load json data - with open(fpath, "r") as opened_file: - lines = opened_file.read().splitlines() - - # prepare json string - standard_json = "" - for line in lines: - # Remove all whitespace on both sides - line = line.strip() - - # Skip blank lines - if len(line) == 0: - continue - - standard_json += line - - # Check if has extra commas - extra_comma = False - if ",]" in standard_json or ",}" in standard_json: - extra_comma = True - standard_json = standard_json.replace(",]", "]") - standard_json = standard_json.replace(",}", "}") - - global first_run - if extra_comma and first_run: - log.error("Extra comma in json file: \"{}\"".format(fpath)) - - # return empty dict if file is empty - if standard_json == "": - if first_run: - log.error("Empty json file: \"{}\"".format(fpath)) - return {} - - # Try to parse string - try: - return json.loads(standard_json) - - except json.decoder.JSONDecodeError: - # Return empty dict if it is first time that decode error happened - if not first_run: - return {} - - # Repreduce the exact same exception but traceback contains better - # information about position of error in the loaded json - try: - with open(fpath, "r") as opened_file: - json.load(opened_file) - - except json.decoder.JSONDecodeError: - log.warning( - "File has invalid json format \"{}\"".format(fpath), - exc_info=True - ) - - return {} - - -def subkey_merge(_dict, value, keys): - key = keys.pop(0) - if not keys: - _dict[key] = value - return _dict - - if key not in _dict: - _dict[key] = {} - _dict[key] = subkey_merge(_dict[key], value, keys) - - return _dict - - -def load_jsons_from_dir(path, *args, **kwargs): - output = {} - - path = os.path.normpath(path) - if not os.path.exists(path): - # TODO warning - return output - - sub_keys = list(kwargs.pop("subkeys", args)) - for sub_key in tuple(sub_keys): - _path = os.path.join(path, sub_key) - if not os.path.exists(_path): - break - - path = _path - sub_keys.pop(0) - - base_len = len(path) + 1 - for base, _directories, filenames in os.walk(path): - base_items_str = base[base_len:] - if not base_items_str: - base_items = [] - else: - base_items = base_items_str.split(os.path.sep) - - for filename in filenames: - basename, ext = os.path.splitext(filename) - if ext == ".json": - full_path = os.path.join(base, filename) - value = load_json(full_path) - dict_keys = base_items + [basename] - output = subkey_merge(output, value, dict_keys) - - for sub_key in sub_keys: - output = output[sub_key] - return output - - -def studio_presets(*args, **kwargs): - return load_jsons_from_dir(STUDIO_PRESETS_PATH, *args, **kwargs) - - -def global_project_presets(**kwargs): - return load_jsons_from_dir(PROJECT_PRESETS_PATH, **kwargs) - - -def path_to_project_overrides(project_name): - project_configs_path = os.environ["PYPE_PROJECT_CONFIGS"] - dirpath = os.path.join(project_configs_path, project_name) - return os.path.join(dirpath, PROJECT_CONFIGURATION_DIR + ".json") - - -def project_preset_overrides(project_name, **kwargs): - if not project_name: - return {} - - path_to_json = path_to_project_overrides(project_name) - if not os.path.exists(path_to_json): - return {} - return load_json(path_to_json) - - -def merge_overrides(global_dict, override_dict): - if OVERRIDEN_KEY in override_dict: - overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) - else: - overriden_keys = set() - - for key, value in override_dict.items(): - if value == POP_KEY: - global_dict.pop(key) - - elif ( - key in overriden_keys - or key not in global_dict - ): - global_dict[key] = value - - elif isinstance(value, dict) and isinstance(global_dict[key], dict): - global_dict[key] = merge_overrides(global_dict[key], value) - - else: - global_dict[key] = value - return global_dict - - -def apply_overrides(global_presets, project_overrides): - global_presets = copy.deepcopy(global_presets) - if not project_overrides: - return global_presets - return merge_overrides(global_presets, project_overrides) - - -def project_presets(project_name=None, **kwargs): - global_presets = global_project_presets(**kwargs) - - if not project_name: - project_name = os.environ.get("AVALON_PROJECT") - project_overrides = project_preset_overrides(project_name, **kwargs) - - return apply_overrides(global_presets, project_overrides) - - -def replace_inner_schemas(schema_data, schema_collection): - if schema_data["type"] == "schema": - raise ValueError("First item in schema data can't be schema.") - - children = schema_data.get("children") - if not children: - return schema_data - - new_children = [] - for child in children: - if child["type"] != "schema": - new_child = replace_inner_schemas(child, schema_collection) - new_children.append(new_child) - continue - - for schema_name in child["children"]: - new_child = replace_inner_schemas( - schema_collection[schema_name], - schema_collection - ) - new_children.append(new_child) - - schema_data["children"] = new_children - return schema_data - - -class SchemaMissingFileInfo(Exception): - def __init__(self, invalid): - full_path_keys = [] - for item in invalid: - full_path_keys.append("\"{}\"".format("/".join(item))) - - msg = ( - "Schema has missing definition of output file (\"is_file\" key)" - " for keys. [{}]" - ).format(", ".join(full_path_keys)) - super(SchemaMissingFileInfo, self).__init__(msg) - - -def file_keys_from_schema(schema_data): - output = [] - keys = [] - key = schema_data.get("key") - if key: - keys.append(key) - - for child in schema_data["children"]: - if child.get("is_file"): - _keys = copy.deepcopy(keys) - _keys.append(child["key"]) - output.append(_keys) - continue - - for result in file_keys_from_schema(child): - _keys = copy.deepcopy(keys) - _keys.extend(result) - output.append(_keys) - return output - - -def validate_all_has_ending_file(schema_data, is_top=True): - if schema_data.get("is_file"): - return None - - children = schema_data.get("children") - if not children: - return [[schema_data["key"]]] - - invalid = [] - keyless = "key" not in schema_data - for child in children: - result = validate_all_has_ending_file(child, False) - if result is None: - continue - - if keyless: - invalid.extend(result) - else: - for item in result: - new_invalid = [schema_data["key"]] - new_invalid.extend(item) - invalid.append(new_invalid) - - if not invalid: - return None - - if not is_top: - return invalid - - raise SchemaMissingFileInfo(invalid) - - -def validate_schema(schema_data): - # TODO validator for key uniquenes - # TODO validator that is_group key is not before is_file child - # TODO validator that is_group or is_file is not on child without key - validate_all_has_ending_file(schema_data) - - -def gui_schema(subfolder, main_schema_name): - subfolder, main_schema_name - dirpath = os.path.join( - os.path.dirname(os.path.dirname(__file__)), - "config_gui_schema", - subfolder - ) - - loaded_schemas = {} - for filename in os.listdir(dirpath): - basename, ext = os.path.splitext(filename) - if ext != ".json": - continue - - filepath = os.path.join(dirpath, filename) - with open(filepath, "r") as json_stream: - schema_data = json.load(json_stream) - loaded_schemas[basename] = schema_data - - main_schema = replace_inner_schemas( - loaded_schemas[main_schema_name], - loaded_schemas - ) - validate_schema(main_schema) - return main_schema diff --git a/pype/tools/config_setting/widgets/lib.py b/pype/tools/config_setting/widgets/lib.py deleted file mode 100644 index d733396d59..0000000000 --- a/pype/tools/config_setting/widgets/lib.py +++ /dev/null @@ -1,54 +0,0 @@ -from .config import OVERRIDEN_KEY - - -class CustomNone: - """Created object can be used as custom None (not equal to None).""" - def __bool__(self): - """Return False (like default None).""" - return False - - -NOT_SET = CustomNone() -AS_WIDGET = type("AS_WIDGET", (), {}) - -METADATA_KEY = type("METADATA_KEY", (), {}) - -OVERRIDE_VERSION = 1 - - -def convert_gui_data_to_overrides(data, first=True): - if not data or not isinstance(data, dict): - return data - - output = {} - if first: - output["__override_version__"] = OVERRIDE_VERSION - - if METADATA_KEY in data: - metadata = data.pop(METADATA_KEY) - for key, value in metadata.items(): - if key == "groups": - output[OVERRIDEN_KEY] = value - else: - KeyError("Unknown metadata key \"{}\"".format(key)) - - for key, value in data.items(): - output[key] = convert_gui_data_to_overrides(value, False) - return output - - -def convert_overrides_to_gui_data(data, first=True): - if not data or not isinstance(data, dict): - return data - - output = {} - if OVERRIDEN_KEY in data: - groups = data.pop(OVERRIDEN_KEY) - if METADATA_KEY not in output: - output[METADATA_KEY] = {} - output[METADATA_KEY]["groups"] = groups - - for key, value in data.items(): - output[key] = convert_overrides_to_gui_data(value, False) - - return output From 428382e070a66758104431527e1c24e0c3fd4ae3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 17:38:22 +0200 Subject: [PATCH 470/947] config temporarily moved to ~/pype/pype directory --- pype/api.py | 2 +- pype/config.py | 193 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 pype/config.py diff --git a/pype/api.py b/pype/api.py index 44a31f2626..e2705f81ea 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,8 +1,8 @@ +from . import config from pypeapp import ( Logger, Anatomy, project_overrides_dir_path, - config, execute ) diff --git a/pype/config.py b/pype/config.py new file mode 100644 index 0000000000..b3e860d72f --- /dev/null +++ b/pype/config.py @@ -0,0 +1,193 @@ +import os +import json +import logging +import copy + +log = logging.getLogger(__name__) + +STUDIO_PRESETS_PATH = os.path.normpath( + os.path.join(os.environ["PYPE_CONFIG"], "config", "studio_presets") +) +PROJECT_CONFIGURATION_DIR = "project_presets" +PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( + os.environ["PYPE_CONFIG"], "config", PROJECT_CONFIGURATION_DIR +)) +first_run = False + +# TODO key popping not implemented yet +POP_KEY = "__pop_key__" +OVERRIDEN_KEY = "__overriden_keys__" + + +def load_json(fpath): + # Load json data + with open(fpath, "r") as opened_file: + lines = opened_file.read().splitlines() + + # prepare json string + standard_json = "" + for line in lines: + # Remove all whitespace on both sides + line = line.strip() + + # Skip blank lines + if len(line) == 0: + continue + + standard_json += line + + # Check if has extra commas + extra_comma = False + if ",]" in standard_json or ",}" in standard_json: + extra_comma = True + standard_json = standard_json.replace(",]", "]") + standard_json = standard_json.replace(",}", "}") + + global first_run + if extra_comma and first_run: + log.error("Extra comma in json file: \"{}\"".format(fpath)) + + # return empty dict if file is empty + if standard_json == "": + if first_run: + log.error("Empty json file: \"{}\"".format(fpath)) + return {} + + # Try to parse string + try: + return json.loads(standard_json) + + except json.decoder.JSONDecodeError: + # Return empty dict if it is first time that decode error happened + if not first_run: + return {} + + # Repreduce the exact same exception but traceback contains better + # information about position of error in the loaded json + try: + with open(fpath, "r") as opened_file: + json.load(opened_file) + + except json.decoder.JSONDecodeError: + log.warning( + "File has invalid json format \"{}\"".format(fpath), + exc_info=True + ) + + return {} + + +def subkey_merge(_dict, value, keys): + key = keys.pop(0) + if not keys: + _dict[key] = value + return _dict + + if key not in _dict: + _dict[key] = {} + _dict[key] = subkey_merge(_dict[key], value, keys) + + return _dict + + +def load_jsons_from_dir(path, *args, **kwargs): + output = {} + + path = os.path.normpath(path) + if not os.path.exists(path): + # TODO warning + return output + + sub_keys = list(kwargs.pop("subkeys", args)) + for sub_key in tuple(sub_keys): + _path = os.path.join(path, sub_key) + if not os.path.exists(_path): + break + + path = _path + sub_keys.pop(0) + + base_len = len(path) + 1 + for base, _directories, filenames in os.walk(path): + base_items_str = base[base_len:] + if not base_items_str: + base_items = [] + else: + base_items = base_items_str.split(os.path.sep) + + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext == ".json": + full_path = os.path.join(base, filename) + value = load_json(full_path) + dict_keys = base_items + [basename] + output = subkey_merge(output, value, dict_keys) + + for sub_key in sub_keys: + output = output[sub_key] + return output + + +def studio_presets(*args, **kwargs): + return load_jsons_from_dir(STUDIO_PRESETS_PATH, *args, **kwargs) + + +def global_project_presets(**kwargs): + return load_jsons_from_dir(PROJECT_PRESETS_PATH, **kwargs) + + +def path_to_project_overrides(project_name): + project_configs_path = os.environ["PYPE_PROJECT_CONFIGS"] + dirpath = os.path.join(project_configs_path, project_name) + return os.path.join(dirpath, PROJECT_CONFIGURATION_DIR + ".json") + + +def project_preset_overrides(project_name, **kwargs): + if not project_name: + return {} + + path_to_json = path_to_project_overrides(project_name) + if not os.path.exists(path_to_json): + return {} + return load_json(path_to_json) + + +def merge_overrides(global_dict, override_dict): + if OVERRIDEN_KEY in override_dict: + overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) + else: + overriden_keys = set() + + for key, value in override_dict.items(): + if value == POP_KEY: + global_dict.pop(key) + + elif ( + key in overriden_keys + or key not in global_dict + ): + global_dict[key] = value + + elif isinstance(value, dict) and isinstance(global_dict[key], dict): + global_dict[key] = merge_overrides(global_dict[key], value) + + else: + global_dict[key] = value + return global_dict + + +def apply_overrides(global_presets, project_overrides): + global_presets = copy.deepcopy(global_presets) + if not project_overrides: + return global_presets + return merge_overrides(global_presets, project_overrides) + + +def project_presets(project_name=None, **kwargs): + global_presets = global_project_presets(**kwargs) + + if not project_name: + project_name = os.environ.get("AVALON_PROJECT") + project_overrides = project_preset_overrides(project_name, **kwargs) + + return apply_overrides(global_presets, project_overrides) From 8f84f78d5d57d5e80014b888219c5761edd144de Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 17:47:26 +0200 Subject: [PATCH 471/947] modified imports --- pype/tools/config_setting/__main__.py | 1 - .../config_setting/config_setting/__init__.py | 2 -- .../config_setting/widgets/__init__.py | 25 ++++++------------- .../widgets/{main.py => window.py} | 0 4 files changed, 7 insertions(+), 21 deletions(-) rename pype/tools/config_setting/config_setting/widgets/{main.py => window.py} (100%) diff --git a/pype/tools/config_setting/__main__.py b/pype/tools/config_setting/__main__.py index 171b85a775..aa6f707443 100644 --- a/pype/tools/config_setting/__main__.py +++ b/pype/tools/config_setting/__main__.py @@ -1,4 +1,3 @@ -import os import sys import config_setting diff --git a/pype/tools/config_setting/config_setting/__init__.py b/pype/tools/config_setting/config_setting/__init__.py index 835754e6a1..0c2fd6d4bb 100644 --- a/pype/tools/config_setting/config_setting/__init__.py +++ b/pype/tools/config_setting/config_setting/__init__.py @@ -1,10 +1,8 @@ from . import style -# from . import widgets from .widgets import MainWidget __all__ = ( "style", - # "widgets", "MainWidget" ) diff --git a/pype/tools/config_setting/config_setting/widgets/__init__.py b/pype/tools/config_setting/config_setting/widgets/__init__.py index 0197917596..0682f00324 100644 --- a/pype/tools/config_setting/config_setting/widgets/__init__.py +++ b/pype/tools/config_setting/config_setting/widgets/__init__.py @@ -1,19 +1,8 @@ -from .lib import ( - NOT_SET, - AS_WIDGET, - METADATA_KEY, - OVERRIDE_VERSION, - convert_gui_data_to_overrides, - convert_overrides_to_gui_data, - TypeToKlass -) +from .window import MainWidget +# TODO properly register inputs to TypeToKlass class +from . import inputs - -from .base import ( - PypeConfigurationWidget, - StudioWidget, - ProjectWidget -) -from .main import MainWidget - -from .inputs import * +__all__ = [ + "MainWidget", + "inputs" +] diff --git a/pype/tools/config_setting/config_setting/widgets/main.py b/pype/tools/config_setting/config_setting/widgets/window.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/main.py rename to pype/tools/config_setting/config_setting/widgets/window.py From 1a5389dc5e16ea5968d8d6d9991e1bee5e0f2099 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 18:44:28 +0200 Subject: [PATCH 472/947] cleaned attribute definitions --- .../config_setting/widgets/base.py | 50 +--- .../config_setting/widgets/inputs.py | 264 +++++------------- .../config_setting/widgets/lib.py | 1 - 3 files changed, 80 insertions(+), 235 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index dcbaf743a9..af979c0f9f 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -7,50 +7,7 @@ from . import lib from avalon import io -class PypeConfigurationWidget: - default_state = "" - - def config_value(self): - raise NotImplementedError( - "Method `config_value` is not implemented for `{}`.".format( - self.__class__.__name__ - ) - ) - - def value_from_values(self, values, keys=None): - if not values: - return lib.NOT_SET - - if keys is None: - keys = self.keys - - value = values - for key in keys: - if not isinstance(value, dict): - raise TypeError( - "Expected dictionary got {}.".format(str(type(value))) - ) - - if key not in value: - return lib.NOT_SET - value = value[key] - return value - - def style_state(self, is_overriden, is_modified): - items = [] - if is_overriden: - items.append("overriden") - if is_modified: - items.append("modified") - return "-".join(items) or self.default_state - - def add_children_gui(self, child_configuration, values): - raise NotImplementedError(( - "Method `add_children_gui` is not implemented for `{}`." - ).format(self.__class__.__name__)) - - -class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class StudioWidget(QtWidgets.QWidget): is_overidable = False is_overriden = False is_group = False @@ -149,6 +106,7 @@ class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): if not os.path.exists(dirpath): os.makedirs(dirpath) + print("Saving data to: ", output_path) with open(output_path, "w") as file_stream: json.dump(origin_values, file_stream, indent=4) @@ -286,7 +244,7 @@ class ProjectListWidget(QtWidgets.QWidget): ) -class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class ProjectWidget(QtWidgets.QWidget): is_overriden = False is_group = False any_parent_is_group = False @@ -407,6 +365,7 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): if not os.path.exists(dirpath): os.makedirs(dirpath) + print("Saving data to: ", overrides_json_path) with open(overrides_json_path, "w") as file_stream: json.dump(output_data, file_stream, indent=4) @@ -457,5 +416,6 @@ class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): if not os.path.exists(dirpath): os.makedirs(dirpath) + print("Saving data to: ", output_path) with open(output_path, "w") as file_stream: json.dump(origin_values, file_stream, indent=4) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 63415a16d3..4e49406e26 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1,7 +1,6 @@ import json from Qt import QtWidgets, QtCore, QtGui from pype.api import config -from .base import PypeConfigurationWidget from .widgets import ( ClickableWidget, ExpandingWidget, @@ -19,14 +18,73 @@ class SchemeGroupHierarchyBug(Exception): super(SchemeGroupHierarchyBug, self).__init__(msg) -class InputWidget: +class ConfigWidget: + default_state = "" + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def config_value(self): + raise NotImplementedError( + "Method `config_value` is not implemented for `{}`.".format( + self.__class__.__name__ + ) + ) + + def value_from_values(self, values, keys=None): + if not values: + return NOT_SET + + if keys is None: + keys = self.keys + + value = values + for key in keys: + if not isinstance(value, dict): + raise TypeError( + "Expected dictionary got {}.".format(str(type(value))) + ) + + if key not in value: + return NOT_SET + value = value[key] + return value + + def style_state(self, is_overriden, is_modified): + items = [] + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or self.default_state + + def add_children_gui(self, child_configuration, values): + raise NotImplementedError(( + "Method `add_children_gui` is not implemented for `{}`." + ).format(self.__class__.__name__)) + + +class InputWidget(ConfigWidget): def overrides(self): if not self.is_overriden: return NOT_SET, False return self.config_value(), self.is_group + @property + def child_modified(self): + return self.is_modified -class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): + @property + def child_overriden(self): + return self._is_overriden + + +class BooleanWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -120,30 +178,14 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): self.set_value(value) self.update_style() - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -185,7 +227,7 @@ class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): return {self.key: self.item_value()} -class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): +class IntegerWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -243,30 +285,14 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): self.int_input.valueChanged.connect(self._on_value_change) - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def set_value(self, value, *, default_value=False): self.int_input.setValue(value) if default_value: @@ -329,7 +355,7 @@ class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): return {self.key: self.item_value()} -class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): +class FloatWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -397,30 +423,14 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): self.float_input.valueChanged.connect(self._on_value_change) - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def set_value(self, value, *, default_value=False): self.float_input.setValue(value) if default_value: @@ -481,7 +491,7 @@ class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): return {self.key: self.item_value()} -class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): +class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -539,30 +549,14 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidg self.text_input.textChanged.connect(self._on_value_change) - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def set_value(self, value, *, default_value=False): self.text_input.setText(value) if default_value: @@ -625,7 +619,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidg return {self.key: self.item_value()} -class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): +class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -681,30 +675,14 @@ class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidge self.text_input.textChanged.connect(self._on_value_change) - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: @@ -822,7 +800,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.update_style(is_valid) -class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): +class RawJsonWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -879,30 +857,14 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): self.text_input.textChanged.connect(self._on_value_change) - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: @@ -963,7 +925,7 @@ class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): return {self.key: self.item_value()} -class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): +class TextListItem(QtWidgets.QWidget, ConfigWidget): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1013,7 +975,7 @@ class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): return self.text_input.text() -class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): @@ -1123,7 +1085,7 @@ class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: self.item_value()} -class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): +class TextListWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) def __init__( @@ -1180,30 +1142,14 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): self.default_value = self.item_value() self.override_value = None - @property - def child_modified(self): - return self.is_modified - - @property - def child_overriden(self): - return self._is_overriden - @property def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1256,7 +1202,7 @@ class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget, InputWidget): return {self.key: self.item_value()} -class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): +class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1323,18 +1269,10 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): def any_parent_is_group(self): return self._parent.any_parent_is_group - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def is_key_modified(self): return self._key() != self.default_key @@ -1375,7 +1313,7 @@ class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): return {key: value} -class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): @@ -1408,10 +1346,6 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): self.default_value = self.config_value() self.override_value = None - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._parent.is_overriden @@ -1420,10 +1354,6 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_group(self): return self._parent.is_group - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - @property def any_parent_is_group(self): return self._parent.any_parent_is_group @@ -1492,7 +1422,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): return output -class ModifiableDict(ExpandingWidget, PypeConfigurationWidget, InputWidget): +class ModifiableDict(ExpandingWidget, InputWidget): # Should be used only for dictionary with one datatype as value # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) @@ -1554,34 +1484,14 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget, InputWidget): self.update_style() - @property - def child_modified(self): - return self.is_modified - @property def is_modified(self): return self._is_modified - @property - def child_overriden(self): - return self._is_overriden - - @property - def is_overidable(self): - return self._parent.is_overidable - @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def is_modified(self): - return self._is_modified - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def apply_overrides(self, override_value): self._state = None self._is_modified = False @@ -1623,7 +1533,7 @@ class ModifiableDict(ExpandingWidget, PypeConfigurationWidget, InputWidget): return {self.key: self.item_value()} -class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): +class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) def __init__( @@ -1734,10 +1644,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -1830,10 +1736,6 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): def config_value(self): return {self.key: self.item_value()} - @property - def is_overidable(self): - return self._parent.is_overidable - def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) @@ -1864,7 +1766,7 @@ class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: values}, self.is_group -class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): +class DictInvisible(QtWidgets.QWidget, ConfigWidget): # TODO is not overridable by itself value_changed = QtCore.Signal(object) @@ -1915,10 +1817,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): def is_overriden(self): return self._is_overriden or self._parent.is_overriden - @property - def is_overidable(self): - return self._parent.is_overidable - @property def child_modified(self): for input_field in self.input_fields: @@ -1933,10 +1831,6 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): return True return False - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def item_value(self): output = {} for input_field in self.input_fields: @@ -2019,7 +1913,7 @@ class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): return {self.key: values}, self.is_group -class DictFormWidget(QtWidgets.QWidget): +class DictFormWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) def __init__( @@ -2070,14 +1964,6 @@ class DictFormWidget(QtWidgets.QWidget): return True return False - @property - def is_overidable(self): - return self._parent.is_overidable - - @property - def ignore_value_changes(self): - return self._parent.ignore_value_changes - def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] key = child_configuration["key"] diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index 454c0b07ed..c6379b4816 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -55,7 +55,6 @@ def convert_overrides_to_gui_data(data, first=True): return output - def replace_inner_schemas(schema_data, schema_collection): if schema_data["type"] == "schema": raise ValueError("First item in schema data can't be schema.") From 8f4cb392f8ba967491bcfb9febd19ea1510219db Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 18:56:12 +0200 Subject: [PATCH 473/947] _is_overriden is global attribute --- .../config_setting/widgets/inputs.py | 70 ++----------------- 1 file changed, 7 insertions(+), 63 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4e49406e26..8f6908eb3a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -20,6 +20,11 @@ class SchemeGroupHierarchyBug(Exception): class ConfigWidget: default_state = "" + _is_overriden = False + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden @property def is_overidable(self): @@ -107,7 +112,6 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False self._was_overriden = False - self._is_overriden = False self._state = None @@ -182,10 +186,6 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -250,7 +250,6 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False self._was_overriden = False - self._is_overriden = False self._state = None @@ -289,10 +288,6 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def set_value(self, value, *, default_value=False): self.int_input.setValue(value) if default_value: @@ -378,7 +373,6 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False self._was_overriden = False - self._is_overriden = False self._state = None @@ -427,10 +421,6 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def set_value(self, value, *, default_value=False): self.float_input.setValue(value) if default_value: @@ -514,7 +504,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False self._was_overriden = False - self._is_overriden = False self._state = None @@ -553,10 +542,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def set_value(self, value, *, default_value=False): self.text_input.setText(value) if default_value: @@ -642,7 +627,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False self._was_overriden = False - self._is_overriden = False self._state = None @@ -679,10 +663,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: @@ -823,7 +803,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False self._was_overriden = False - self._is_overriden = False self._state = None @@ -861,10 +840,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: @@ -1107,7 +1082,6 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self.is_group = is_group self._was_overriden = False - self._is_overriden = False self._state = None @@ -1146,10 +1120,6 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): def is_modified(self): return self._is_modified or (self._was_overriden != self.is_overriden) - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1269,10 +1239,6 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): def any_parent_is_group(self): return self._parent.any_parent_is_group - @property - def is_overriden(self): - return self._parent.is_overriden - def is_key_modified(self): return self._key() != self.default_key @@ -1346,10 +1312,6 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): self.default_value = self.config_value() self.override_value = None - @property - def is_overriden(self): - return self._parent.is_overriden - @property def is_group(self): return self._parent.is_group @@ -1448,7 +1410,6 @@ class ModifiableDict(ExpandingWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._is_overriden = False self._was_overriden = False self._state = None @@ -1488,10 +1449,6 @@ class ModifiableDict(ExpandingWidget, InputWidget): def is_modified(self): return self._is_modified - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def apply_overrides(self, override_value): self._state = None self._is_modified = False @@ -1533,6 +1490,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): return {self.key: self.item_value()} +# Dictionaries class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) @@ -1556,7 +1514,6 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): self.any_parent_is_group = any_parent_is_group self._is_modified = False - self._is_overriden = False self.is_group = is_group self._state = None @@ -1640,10 +1597,6 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): super(DictExpandWidget, self).resizeEvent(event) self.content_widget.updateGeometry() - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -1785,7 +1738,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): self.any_parent_is_group = any_parent_is_group - self._is_overriden = False self.is_modified = False self.is_group = is_group @@ -1813,10 +1765,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): def update_style(self, *args, **kwargs): return - @property - def is_overriden(self): - return self._is_overriden or self._parent.is_overriden - @property def child_modified(self): for input_field in self.input_fields: @@ -1913,6 +1861,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): return {self.key: values}, self.is_group +# Proxy for form layout class DictFormWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) @@ -1928,7 +1877,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigWidget): self.any_parent_is_group = any_parent_is_group self.is_modified = False - self._is_overriden = False self.is_group = False super(DictFormWidget, self).__init__(parent) @@ -1946,10 +1894,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigWidget): return self.value_changed.emit(self) - @property - def is_overriden(self): - return self._parent.is_overriden - @property def child_modified(self): for input_field in self.input_fields.values(): From 949c844cc1b85648a99e3f82336a55f161f50230 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 19:01:32 +0200 Subject: [PATCH 474/947] was overriden as global attribute --- .../config_setting/widgets/inputs.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8f6908eb3a..bbc5387c02 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -21,11 +21,16 @@ class SchemeGroupHierarchyBug(Exception): class ConfigWidget: default_state = "" _is_overriden = False + _was_overriden = False @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden + @property + def was_overriden(self): + return self._was_overriden + @property def is_overidable(self): return self._parent.is_overidable @@ -111,7 +116,6 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None @@ -184,7 +188,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -249,7 +253,6 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None @@ -286,7 +289,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def set_value(self, value, *, default_value=False): self.int_input.setValue(value) @@ -372,7 +375,6 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None @@ -419,7 +421,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def set_value(self, value, *, default_value=False): self.float_input.setValue(value) @@ -503,7 +505,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None @@ -540,7 +541,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def set_value(self, value, *, default_value=False): self.text_input.setText(value) @@ -626,7 +627,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None @@ -661,7 +661,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) @@ -802,7 +802,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None @@ -838,7 +837,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) @@ -1081,7 +1080,6 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self.is_group = is_group - self._was_overriden = False self._state = None @@ -1118,7 +1116,7 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): @property def is_modified(self): - return self._is_modified or (self._was_overriden != self.is_overriden) + return self._is_modified or (self.was_overriden != self.is_overriden) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -1410,7 +1408,6 @@ class ModifiableDict(ExpandingWidget, InputWidget): self.is_group = is_group self._is_modified = False - self._was_overriden = False self._state = None super(ModifiableDict, self).__init__(input_data["label"], parent) From aaab8b4bf014fee3f6a728121b1c3d78788ebfe7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 19:08:40 +0200 Subject: [PATCH 475/947] working path to config --- pype/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/config.py b/pype/config.py index b3e860d72f..488492d722 100644 --- a/pype/config.py +++ b/pype/config.py @@ -6,11 +6,11 @@ import copy log = logging.getLogger(__name__) STUDIO_PRESETS_PATH = os.path.normpath( - os.path.join(os.environ["PYPE_CONFIG"], "config", "studio_presets") + os.path.join(os.environ["PYPE_CONFIG"], "studio_presets") ) PROJECT_CONFIGURATION_DIR = "project_presets" PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( - os.environ["PYPE_CONFIG"], "config", PROJECT_CONFIGURATION_DIR + os.environ["PYPE_CONFIG"], PROJECT_CONFIGURATION_DIR )) first_run = False From 91158eaed98af947a2a487595d5f9c0cfe19c9ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 19:10:00 +0200 Subject: [PATCH 476/947] is modified is global attribute --- .../config_setting/widgets/inputs.py | 49 ++----------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index bbc5387c02..72dd0932e7 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -21,8 +21,13 @@ class SchemeGroupHierarchyBug(Exception): class ConfigWidget: default_state = "" _is_overriden = False + _is_modified = False _was_overriden = False + @property + def is_modified(self): + return self._is_modified or (self.was_overriden != self.is_overriden) + @property def is_overriden(self): return self._is_overriden or self._parent.is_overriden @@ -115,7 +120,6 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): is_group = True self.is_group = is_group - self._is_modified = False self._state = None @@ -186,10 +190,6 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self.set_value(value) self.update_style() - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -252,7 +252,6 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): is_group = True self.is_group = is_group - self._is_modified = False self._state = None @@ -287,10 +286,6 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): self.int_input.valueChanged.connect(self._on_value_change) - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def set_value(self, value, *, default_value=False): self.int_input.setValue(value) if default_value: @@ -374,7 +369,6 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): is_group = True self.is_group = is_group - self._is_modified = False self._state = None @@ -419,10 +413,6 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self.float_input.valueChanged.connect(self._on_value_change) - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def set_value(self, value, *, default_value=False): self.float_input.setValue(value) if default_value: @@ -504,7 +494,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): is_group = True self.is_group = is_group - self._is_modified = False self._state = None @@ -539,10 +528,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): self.text_input.textChanged.connect(self._on_value_change) - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def set_value(self, value, *, default_value=False): self.text_input.setText(value) if default_value: @@ -626,7 +611,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): is_group = True self.is_group = is_group - self._is_modified = False self._state = None @@ -659,10 +643,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self.text_input.textChanged.connect(self._on_value_change) - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: @@ -801,7 +781,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): is_group = True self.is_group = is_group - self._is_modified = False self._state = None @@ -835,10 +814,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.text_input.textChanged.connect(self._on_value_change) - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def set_value(self, value, *, default_value=False): self.text_input.setPlainText(value) if default_value: @@ -1078,7 +1053,6 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): if not any_parent_is_group and not is_group: is_group = True - self._is_modified = False self.is_group = is_group self._state = None @@ -1114,10 +1088,6 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self.default_value = self.item_value() self.override_value = None - @property - def is_modified(self): - return self._is_modified or (self.was_overriden != self.is_overriden) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1407,7 +1377,6 @@ class ModifiableDict(ExpandingWidget, InputWidget): self.any_parent_is_group = any_parent_is_group self.is_group = is_group - self._is_modified = False self._state = None super(ModifiableDict, self).__init__(input_data["label"], parent) @@ -1442,10 +1411,6 @@ class ModifiableDict(ExpandingWidget, InputWidget): self.update_style() - @property - def is_modified(self): - return self._is_modified - def apply_overrides(self, override_value): self._state = None self._is_modified = False @@ -1510,7 +1475,6 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): self.any_parent_is_group = any_parent_is_group - self._is_modified = False self.is_group = is_group self._state = None @@ -1734,8 +1698,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): raise SchemeGroupHierarchyBug() self.any_parent_is_group = any_parent_is_group - - self.is_modified = False self.is_group = is_group super(DictInvisible, self).__init__(parent) @@ -1873,7 +1835,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigWidget): self.any_parent_is_group = any_parent_is_group - self.is_modified = False self.is_group = False super(DictFormWidget, self).__init__(parent) From 0acbc5459eb65acc5b216913578e6df5692b1a23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 19:17:41 +0200 Subject: [PATCH 477/947] config_value is global method --- .../config_setting/widgets/inputs.py | 57 +++++++------------ 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 72dd0932e7..ee8c800b29 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -44,13 +44,26 @@ class ConfigWidget: def ignore_value_changes(self): return self._parent.ignore_value_changes - def config_value(self): + def reset_attributes(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + + self.reset_children_attributes() + + def reset_children_attributes(self): raise NotImplementedError( - "Method `config_value` is not implemented for `{}`.".format( - self.__class__.__name__ - ) + "Method `reset_children_attributes` not implemented!" ) + def item_value(self): + raise NotImplementedError( + "Method `item_value` not implemented!" + ) + + def config_value(self): + return {self.key: self.item_value()} + def value_from_values(self, values, keys=None): if not values: return NOT_SET @@ -98,6 +111,9 @@ class InputWidget(ConfigWidget): def child_overriden(self): return self._is_overriden + def reset_children_attributes(self): + return + class BooleanWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) @@ -227,9 +243,6 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.checkbox.isChecked() - def config_value(self): - return {self.key: self.item_value()} - class IntegerWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) @@ -344,9 +357,6 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.int_input.value() - def config_value(self): - return {self.key: self.item_value()} - class FloatWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) @@ -469,9 +479,6 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.float_input.value() - def config_value(self): - return {self.key: self.item_value()} - class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) @@ -586,9 +593,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.text_input.text() - def config_value(self): - return {self.key: self.item_value()} - class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) @@ -699,9 +703,6 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.text_input.toPlainText() - def config_value(self): - return {self.key: self.item_value()} - class RawJsonInput(QtWidgets.QPlainTextEdit): tab_length = 4 @@ -870,9 +871,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.text_input.toPlainText() - def config_value(self): - return {self.key: self.item_value()} - class TextListItem(QtWidgets.QWidget, ConfigWidget): _btn_size = 20 @@ -1030,9 +1028,6 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): return output - def config_value(self): - return {self.key: self.item_value()} - class TextListWidget(QtWidgets.QWidget, InputWidget): value_changed = QtCore.Signal(object) @@ -1136,9 +1131,6 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): def item_value(self): return self.value_widget.config_value() - def config_value(self): - return {self.key: self.item_value()} - class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): _btn_size = 20 @@ -1448,9 +1440,6 @@ class ModifiableDict(ExpandingWidget, InputWidget): def item_value(self): return self.value_widget.config_value() - def config_value(self): - return {self.key: self.item_value()} - # Dictionaries class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): @@ -1647,9 +1636,6 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): output.update(input_field.config_value()) return output - def config_value(self): - return {self.key: self.item_value()} - def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) @@ -1746,9 +1732,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): output.update(input_field.config_value()) return output - def config_value(self): - return {self.key: self.item_value()} - def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] if item_type == "schema": From b402c8d0135057809ce2ce9a61999934d1ea4293 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 19:35:44 +0200 Subject: [PATCH 478/947] removed overrides --- .../kuba_each_case/project_presets.json | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json deleted file mode 100644 index b9da242453..0000000000 --- a/pype/tools/config_setting/config/project_overrides/kuba_each_case/project_presets.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "__override_version__": 1, - "plugins": { - "maya": { - "__overriden_keys__": [ - "publish" - ], - "publish": { - "ValidateModelName": { - "enabled": true - }, - "ValidateAssemblyName": { - "enabled": false - } - } - } - } -} \ No newline at end of file From 71c2bb3ec1a5da3b68b5709db3bc6c287c3b0d11 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Aug 2020 22:32:44 +0200 Subject: [PATCH 479/947] moved type up --- .../applications_gui_schema.json | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json index 12fbb3cc26..bbf74a8f3f 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json @@ -9,144 +9,144 @@ "type": "dict-form", "children": [ { - "key": "blender_2.80", "type": "boolean", + "key": "blender_2.80", "label": "Blender 2.80" }, { - "key": "blender_2.81", "type": "boolean", + "key": "blender_2.81", "label": "Blender 2.81" }, { - "key": "blender_2.82", "type": "boolean", + "key": "blender_2.82", "label": "Blender 2.82" }, { - "key": "blender_2.83", "type": "boolean", + "key": "blender_2.83", "label": "Blender 2.83" }, { - "key": "celaction_local", "type": "boolean", + "key": "celaction_local", "label": "Celaction Local" }, { - "key": "celaction_remote", "type": "boolean", + "key": "celaction_remote", "label": "Celaction Remote" }, { - "key": "harmony_17", "type": "boolean", + "key": "harmony_17", "label": "Harmony 17" }, { - "key": "houdini_16", "type": "boolean", + "key": "houdini_16", "label": "Houdini 16" }, { - "key": "houdini_17", "type": "boolean", + "key": "houdini_17", "label": "Houdini 17" }, { - "key": "houdini_18", "type": "boolean", + "key": "houdini_18", "label": "Houdini 18" }, { - "key": "maya_2017", "type": "boolean", + "key": "maya_2017", "label": "Autodest Maya 2017" }, { - "key": "maya_2018", "type": "boolean", + "key": "maya_2018", "label": "Autodest Maya 2018" }, { - "key": "maya_2019", "type": "boolean", + "key": "maya_2019", "label": "Autodest Maya 2019" }, { - "key": "maya_2020", "type": "boolean", + "key": "maya_2020", "label": "Autodest Maya 2020" }, { "key": "nuke_10.0", "type": "boolean", "label": "Nuke 10.0" }, { - "key": "nuke_11.2", "type": "boolean", + "key": "nuke_11.2", "label": "Nuke 11.2" }, { - "key": "nuke_11.3", "type": "boolean", + "key": "nuke_11.3", "label": "Nuke 11.3" }, { - "key": "nuke_12.0", "type": "boolean", + "key": "nuke_12.0", "label": "Nuke 12.0" }, { - "key": "nukex_10.0", "type": "boolean", + "key": "nukex_10.0", "label": "NukeX 10.0" }, { - "key": "nukex_11.2", "type": "boolean", + "key": "nukex_11.2", "label": "NukeX 11.2" }, { - "key": "nukex_11.3", "type": "boolean", + "key": "nukex_11.3", "label": "NukeX 11.3" }, { - "key": "nukex_12.0", "type": "boolean", + "key": "nukex_12.0", "label": "NukeX 12.0" }, { - "key": "nukestudio_10.0", "type": "boolean", + "key": "nukestudio_10.0", "label": "NukeStudio 10.0" }, { - "key": "nukestudio_11.2", "type": "boolean", + "key": "nukestudio_11.2", "label": "NukeStudio 11.2" }, { - "key": "nukestudio_11.3", "type": "boolean", + "key": "nukestudio_11.3", "label": "NukeStudio 11.3" }, { - "key": "nukestudio_12.0", "type": "boolean", + "key": "nukestudio_12.0", "label": "NukeStudio 12.0" }, { - "key": "houdini_16.5", "type": "boolean", + "key": "houdini_16.5", "label": "Houdini 16.5" }, { - "key": "houdini_17", "type": "boolean", + "key": "houdini_17", "label": "Houdini 17" }, { - "key": "houdini_18", "type": "boolean", + "key": "houdini_18", "label": "Houdini 18" }, { - "key": "premiere_2019", "type": "boolean", + "key": "premiere_2019", "label": "Premiere 2019" }, { - "key": "premiere_2020", "type": "boolean", + "key": "premiere_2020", "label": "Premiere 2020" }, { - "key": "premiere_2020", "type": "boolean", + "key": "premiere_2020", "label": "Premiere 2020" }, { + "type": "boolean", "key": "resolve_16", - "type": "boolean", "label": "BM DaVinci Resolve 16" }, { - "key": "storyboardpro_7", "type": "boolean", + "key": "storyboardpro_7", "label": "Storyboard Pro 7" }, { - "key": "unreal_4.24", "type": "boolean", + "key": "unreal_4.24", "label": "Unreal Editor 4.24" } ] From 5f0ea1378c5d9c5fc34fdcc91f7fd03698f77584 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 23 Aug 2020 19:16:50 +0200 Subject: [PATCH 480/947] indexes should be removed properly now for artist view --- pype/tools/pyblish_pype/model.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pype/tools/pyblish_pype/model.py b/pype/tools/pyblish_pype/model.py index fdcdffd33f..3c9d4806ac 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/pype/tools/pyblish_pype/model.py @@ -870,13 +870,18 @@ class ArtistProxy(QtCore.QAbstractProxyModel): self.rowsInserted.emit(self.parent(), new_from, new_to + 1) def _remove_rows(self, parent_row, from_row, to_row): - removed_rows = [] increment_num = self.mapping_from[parent_row][from_row] + + to_end_index = len(self.mapping_from[parent_row]) - 1 + for _idx in range(0, parent_row): + to_end_index += len(self.mapping_from[_idx]) + + removed_rows = 0 _emit_last = None for row_num in reversed(range(from_row, to_row + 1)): row = self.mapping_from[parent_row].pop(row_num) _emit_last = row - removed_rows.append(row) + removed_rows += 1 _emit_first = int(increment_num) mapping_from_len = len(self.mapping_from) @@ -896,11 +901,8 @@ class ArtistProxy(QtCore.QAbstractProxyModel): self.mapping_from[idx_i][idx_j] = increment_num increment_num += 1 - first_to_row = None - for row in removed_rows: - if first_to_row is None: - first_to_row = row - self.mapping_to.pop(row) + for idx in range(removed_rows): + self.mapping_to.pop(to_end_index - idx) return (_emit_first, _emit_last) From a5e6979a86009d56a52a7ccf41651e4a51277dac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 10:52:52 +0200 Subject: [PATCH 481/947] renamed gui schemas files and added intent --- ...i_schema.json => 0_studio_gui_schema.json} | 5 +++-- ...ma.json => 1_applications_gui_schema.json} | 0 .../studio_schema/1_intents_gui_schema.json | 19 +++++++++++++++++++ ...ui_schema.json => 1_tools_gui_schema.json} | 0 .../config_setting/widgets/base.py | 2 +- 5 files changed, 23 insertions(+), 3 deletions(-) rename pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/{studio_gui_schema.json => 0_studio_gui_schema.json} (83%) rename pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/{applications_gui_schema.json => 1_applications_gui_schema.json} (100%) create mode 100644 pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json rename pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/{tools_gui_schema.json => 1_tools_gui_schema.json} (100%) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json similarity index 83% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index 088c407804..868fbea82e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -10,8 +10,9 @@ "children": [{ "type": "schema", "children": [ - "applications_gui_schema", - "tools_gui_schema" + "1_applications_gui_schema", + "1_tools_gui_schema", + "1_intents_gui_schema" ] }] }, { diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/applications_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json new file mode 100644 index 0000000000..1469ffc5fc --- /dev/null +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json @@ -0,0 +1,19 @@ +{ + "key": "intent", + "type": "dict-expanding", + "label": "Intent Setting", + "is_group": true, + "is_file": true, + "children": [ + { + "type": "dict-modifiable", + "key": "items", + "label": "Intent Key/Label", + "object_type": "text-singleline" + }, { + "type": "text-singleline", + "key": "default", + "label": "Default intent" + } + ] +} diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/tools_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index af979c0f9f..a794ea64f3 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -62,7 +62,7 @@ class StudioWidget(QtWidgets.QWidget): self.input_fields.clear() values = {"studio": config.studio_presets()} - schema = lib.gui_schema("studio_schema", "studio_gui_schema") + schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) self.schema = schema From df0eae7719495b8cf8bc7740b20e68067cf39e81 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 11:39:38 +0200 Subject: [PATCH 482/947] added tray items --- .../studio_schema/0_studio_gui_schema.json | 15 +-- .../studio_schema/1_muster.json | 12 ++ .../studio_schema/1_tray_items.json | 106 ++++++++++++++++++ 3 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json create mode 100644 pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index 868fbea82e..9063dfcc8e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -10,22 +10,13 @@ "children": [{ "type": "schema", "children": [ + "1_tray_items", "1_applications_gui_schema", "1_tools_gui_schema", - "1_intents_gui_schema" + "1_intents_gui_schema", + "1_muster" ] }] - }, { - "key": "muster", - "type": "dict-invisible", - "is_group": true, - "children": [{ - "key": "templates_mapping", - "label": "Muster", - "is_file": true, - "type": "dict-modifiable", - "object_type": "int" - }] } ] } diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json new file mode 100644 index 0000000000..224f2efc71 --- /dev/null +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json @@ -0,0 +1,12 @@ +{ + "key": "muster", + "type": "dict-invisible", + "is_group": true, + "children": [{ + "key": "templates_mapping", + "label": "Muster", + "is_file": true, + "type": "dict-modifiable", + "object_type": "int" + }] +} diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json new file mode 100644 index 0000000000..0456e52985 --- /dev/null +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -0,0 +1,106 @@ +{ + "key": "tray_modules", + "type": "dict-expanding", + "label": "Modules", + "is_file": true, + "is_group": true, + "children": [ + { + "key": "item_usage", + "type": "dict-invisible", + "children": [ + { + "type": "dict-form", + "children": [ + { + "type": "boolean", + "key": "User settings", + "label": "User settings" + }, { + "type": "boolean", + "key": "Ftrack", + "label": "Ftrack" + }, { + "type": "boolean", + "key": "Muster", + "label": "Muster" + }, { + "type": "boolean", + "key": "Avalon", + "label": "Avalon" + }, { + "type": "boolean", + "key": "Clockify", + "label": "Clockify" + }, { + "type": "boolean", + "key": "Standalone Publish", + "label": "Standalone Publish" + }, { + "type": "boolean", + "key": "Logging", + "label": "Logging" + }, { + "type": "boolean", + "key": "Idle Manager", + "label": "Idle Manager" + }, { + "type": "boolean", + "key": "Rest Api", + "label": "Rest Api" + }, { + "type": "boolean", + "key": "Adobe Communicator", + "label": "Adobe Communicator" + } + ] + } + ] + }, { + "key": "attributes", + "type": "dict-expanding", + "label": "Module attributes", + "children": [ + { + "type": "dict-expanding", + "key": "Rest Api", + "label": "Rest Api", + "MISINGKEYCONF": {"exclude_ports": []}, + "children": [ + { + "type": "int", + "key": "default_port", + "label": "Default Port" + } + ] + }, { + "type": "dict-expanding", + "key": "Timers Manager", + "label": "Timers Manager", + "children": [ + { + "type": "float", + "key": "full_time", + "label": "Max idle time" + }, { + "type": "float", + "key": "message_time", + "label": "When dialog will show" + } + ] + }, { + "type": "dict-expanding", + "key": "Clockify", + "label": "Clockify", + "children": [ + { + "type": "text-singleline", + "key": "workspace_name", + "label": "Workspace name" + } + ] + } + ] + } + ] +} From f32aa4fd84abfa09e53bc4ceb5c07f3bf3d4f391 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 11:48:40 +0200 Subject: [PATCH 483/947] do not show the attributes label --- .../config_gui_schema/studio_schema/1_tray_items.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index 0456e52985..e6f9a41e51 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -58,8 +58,7 @@ ] }, { "key": "attributes", - "type": "dict-expanding", - "label": "Module attributes", + "type": "dict-invisible", "children": [ { "type": "dict-expanding", From 4a375737e346540d0c93422177b9a54c758079ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 12:18:17 +0200 Subject: [PATCH 484/947] moved muster back to studio --- .../studio_schema/0_studio_gui_schema.json | 14 ++++++++++++-- .../config_gui_schema/studio_schema/1_muster.json | 12 ------------ 2 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index 9063dfcc8e..2fd63c7cdc 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -13,10 +13,20 @@ "1_tray_items", "1_applications_gui_schema", "1_tools_gui_schema", - "1_intents_gui_schema", - "1_muster" + "1_intents_gui_schema" ] }] + }, { + "key": "muster", + "type": "dict-invisible", + "children": [{ + "is_group": true, + "is_file": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "type": "dict-modifiable", + "object_type": "int" + }] } ] } diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json deleted file mode 100644 index 224f2efc71..0000000000 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_muster.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "key": "muster", - "type": "dict-invisible", - "is_group": true, - "children": [{ - "key": "templates_mapping", - "label": "Muster", - "is_file": true, - "type": "dict-modifiable", - "object_type": "int" - }] -} From fce265af47a8bb297e0d4b86b1bef88eccc4d1f5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 12:52:46 +0200 Subject: [PATCH 485/947] renamed presets to configurations --- pype/config.py | 12 ++++----- .../config_setting/widgets/base.py | 26 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pype/config.py b/pype/config.py index 488492d722..454835e005 100644 --- a/pype/config.py +++ b/pype/config.py @@ -6,7 +6,7 @@ import copy log = logging.getLogger(__name__) STUDIO_PRESETS_PATH = os.path.normpath( - os.path.join(os.environ["PYPE_CONFIG"], "studio_presets") + os.path.join(os.environ["PYPE_CONFIG"], "studio_configurations") ) PROJECT_CONFIGURATION_DIR = "project_presets" PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( @@ -128,11 +128,11 @@ def load_jsons_from_dir(path, *args, **kwargs): return output -def studio_presets(*args, **kwargs): +def studio_configurations(*args, **kwargs): return load_jsons_from_dir(STUDIO_PRESETS_PATH, *args, **kwargs) -def global_project_presets(**kwargs): +def global_project_configurations(**kwargs): return load_jsons_from_dir(PROJECT_PRESETS_PATH, **kwargs) @@ -142,7 +142,7 @@ def path_to_project_overrides(project_name): return os.path.join(dirpath, PROJECT_CONFIGURATION_DIR + ".json") -def project_preset_overrides(project_name, **kwargs): +def project_configurations_overrides(project_name, **kwargs): if not project_name: return {} @@ -184,10 +184,10 @@ def apply_overrides(global_presets, project_overrides): def project_presets(project_name=None, **kwargs): - global_presets = global_project_presets(**kwargs) + global_presets = global_project_configurations(**kwargs) if not project_name: project_name = os.environ.get("AVALON_PROJECT") - project_overrides = project_preset_overrides(project_name, **kwargs) + project_overrides = project_configurations_overrides(project_name, **kwargs) return apply_overrides(global_presets, project_overrides) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index a794ea64f3..23eabdb531 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -61,7 +61,7 @@ class StudioWidget(QtWidgets.QWidget): widget.deleteLater() self.input_fields.clear() - values = {"studio": config.studio_presets()} + values = {"studio": config.studio_configurations()} schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) @@ -80,14 +80,14 @@ class StudioWidget(QtWidgets.QWidget): all_values = all_values["studio"] # Load studio data with metadata - current_presets = config.studio_presets() + current_configurations = config.studio_configurations() keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: # Skip first key key_sequence = key_sequence[1:] subpath = "/".join(key_sequence) + ".json" - origin_values = current_presets + origin_values = current_configurations for key in key_sequence: if key not in origin_values: origin_values = {} @@ -280,13 +280,13 @@ class ProjectWidget(QtWidgets.QWidget): footer_layout.addWidget(spacer_widget, 1) footer_layout.addWidget(save_btn, 0) - presets_widget = QtWidgets.QWidget() - presets_layout = QtWidgets.QVBoxLayout(presets_widget) - presets_layout.setContentsMargins(0, 0, 0, 0) - presets_layout.setSpacing(0) + configurations_widget = QtWidgets.QWidget() + configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) + configurations_layout.setContentsMargins(0, 0, 0, 0) + configurations_layout.setSpacing(0) - presets_layout.addWidget(scroll_widget, 1) - presets_layout.addWidget(footer_widget, 0) + configurations_layout.addWidget(scroll_widget, 1) + configurations_layout.addWidget(footer_widget, 0) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -294,7 +294,7 @@ class ProjectWidget(QtWidgets.QWidget): self.setLayout(layout) layout.addWidget(project_list_widget, 0) - layout.addWidget(presets_widget, 1) + layout.addWidget(configurations_widget, 1) save_btn.clicked.connect(self._save) project_list_widget.project_changed.connect(self._on_project_change) @@ -307,7 +307,7 @@ class ProjectWidget(QtWidgets.QWidget): self.reset() def reset(self): - values = config.global_project_presets() + values = config.global_project_configurations() schema = lib.gui_schema("projects_schema", "project_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) @@ -390,14 +390,14 @@ class ProjectWidget(QtWidgets.QWidget): all_values = all_values["project"] # Load studio data with metadata - current_presets = config.global_project_presets() + current_configurations = config.global_project_configurations() keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: # Skip first key key_sequence = key_sequence[1:] subpath = "/".join(key_sequence) + ".json" - origin_values = current_presets + origin_values = current_configurations for key in key_sequence: if key not in origin_values: origin_values = {} From eb3406751119d6581bc9380b08110849f05217c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 13:01:04 +0200 Subject: [PATCH 486/947] changed input from vertical to horizontal layout --- .../config_setting/widgets/inputs.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ee8c800b29..3a862b7a3e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -141,9 +141,9 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): super(BooleanWidget, self).__init__(parent) - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout.setSpacing(5) self.checkbox = QtWidgets.QCheckBox() self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) @@ -270,9 +270,9 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): super(IntegerWidget, self).__init__(parent) - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout.setSpacing(5) self.int_input = ModifiedIntSpinBox() @@ -384,9 +384,9 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): super(FloatWidget, self).__init__(parent) - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout.setSpacing(5) self.float_input = ModifiedFloatSpinBox() @@ -506,9 +506,9 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): super(TextSingleLineWidget, self).__init__(parent) - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout.setSpacing(5) self.text_input = QtWidgets.QLineEdit() @@ -620,9 +620,9 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): super(TextMultiLineWidget, self).__init__(parent) - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout.setSpacing(5) self.text_input = QtWidgets.QPlainTextEdit() if not label_widget: From f62d7b8cac476378fb109e64996eeb9d7a5d814c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 14:42:25 +0200 Subject: [PATCH 487/947] removed temp configurations --- .../studio_presets/ftrack/server_plugins.json | 1 - .../studio_presets/ftrack/user_plugins.json | 5 --- .../studio_presets/global/applications.json | 43 ------------------- .../config/studio_presets/global/intents.json | 9 ---- .../config/studio_presets/global/tools.json | 6 --- .../studio_presets/global/tray_items.json | 25 ----------- .../muster/templates_mapping.json | 19 -------- 7 files changed, 108 deletions(-) delete mode 100644 pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json delete mode 100644 pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json delete mode 100644 pype/tools/config_setting/config/studio_presets/global/applications.json delete mode 100644 pype/tools/config_setting/config/studio_presets/global/intents.json delete mode 100644 pype/tools/config_setting/config/studio_presets/global/tools.json delete mode 100644 pype/tools/config_setting/config/studio_presets/global/tray_items.json delete mode 100644 pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json diff --git a/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json b/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json b/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json deleted file mode 100644 index 1ba8e9b511..0000000000 --- a/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "TestAction": { - "ignore_me": true - } -} diff --git a/pype/tools/config_setting/config/studio_presets/global/applications.json b/pype/tools/config_setting/config/studio_presets/global/applications.json deleted file mode 100644 index 21693e5fee..0000000000 --- a/pype/tools/config_setting/config/studio_presets/global/applications.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "blender_2.80": true, - "blender_2.81": true, - "blender_2.82": true, - "blender_2.83": true, - "harmony_17": true, - "houdini_16": true, - "houdini_17": true, - "houdini_18": true, - "maya_2016": true, - "maya_2017": true, - "maya_2018": true, - "maya_2019": true, - "maya_2020": true, - "nuke_10.0": true, - "nuke_11.0": true, - "nuke_11.2": true, - "nuke_11.3": true, - "nuke_12.0": true, - "nukex_10.0": true, - "nukex_11.0": true, - "nukex_11.2": true, - "nukex_11.3": true, - "nukex_12.0": true, - "nukestudio_10.0": true, - "nukestudio_11.0": true, - "nukestudio_11.2": true, - "nukestudio_11.3": true, - "nukestudio_12.0": true, - "photoshop_2020": true, - "premiere_2019": true, - "premiere_2020": true, - "python_2": true, - "python_3": true, - "resolve_16": true, - "shell": true, - "storyboardpro_7": true, - "unreal_4.21": true, - "celaction_local": true, - "celaction_remote": true, - "houdini_16.5": false, - "unreal_4.24": true -} \ No newline at end of file diff --git a/pype/tools/config_setting/config/studio_presets/global/intents.json b/pype/tools/config_setting/config/studio_presets/global/intents.json deleted file mode 100644 index c853e27b7f..0000000000 --- a/pype/tools/config_setting/config/studio_presets/global/intents.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "default": "wip", - "items": { - "": "", - "wip": "WIP", - "test": "TEST", - "final": "FINAL" - } -} diff --git a/pype/tools/config_setting/config/studio_presets/global/tools.json b/pype/tools/config_setting/config/studio_presets/global/tools.json deleted file mode 100644 index 53aab7b2ca..0000000000 --- a/pype/tools/config_setting/config/studio_presets/global/tools.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "mtoa_3.0.1": false, - "mtoa_3.1.1": false, - "mtoa_3.2.0": false, - "yeti_2.1.2": false -} \ No newline at end of file diff --git a/pype/tools/config_setting/config/studio_presets/global/tray_items.json b/pype/tools/config_setting/config/studio_presets/global/tray_items.json deleted file mode 100644 index a42bf67c38..0000000000 --- a/pype/tools/config_setting/config/studio_presets/global/tray_items.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "usage": { - "User settings": false, - "Ftrack": false, - "Muster": false, - "Avalon": true, - "Clockify": false, - "Standalone Publish": true, - "Logging": true, - "Idle Manager": true, - "Timers Manager": true, - "Rest Api": true, - "Premiere Communicator": true - }, - "attributes": { - "Rest Api": { - "default_port": 8021, - "exclude_ports": [] - }, - "Timers Manager": { - "full_time": 15, - "message_time": 0.5 - } - } -} diff --git a/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json b/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json deleted file mode 100644 index 0c09113515..0000000000 --- a/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "3delight": 41, - "arnold": 46, - "arnold_sf": 57, - "gelato": 30, - "harware": 3, - "krakatoa": 51, - "file_layers": 7, - "mentalray": 2, - "mentalray_sf": 6, - "redshift": 55, - "renderman": 29, - "software": 1, - "software_sf": 5, - "turtle": 10, - "vector": 4, - "vray": 37, - "ffmpeg": 48 -} \ No newline at end of file From b13c05919a50a4df8546a2b47ed7c1de0e39ede6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 14:58:07 +0200 Subject: [PATCH 488/947] changed names of project schemas --- ..._schema.json => 0_project_gui_schema.json} | 5 ++- ...json => 1_ftrack_projects_gui_schema.json} | 0 ..._schema.json => 1_plugins_gui_schema.json} | 10 +++--- .../projects_schema/test_project_schema.json | 34 ------------------- .../config_setting/widgets/base.py | 2 +- 5 files changed, 8 insertions(+), 43 deletions(-) rename pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/{project_gui_schema.json => 0_project_gui_schema.json} (58%) rename pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/{ftrack_projects_gui_schema.json => 1_ftrack_projects_gui_schema.json} (100%) rename pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/{plugins_gui_schema.json => 1_plugins_gui_schema.json} (100%) delete mode 100644 pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/test_project_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json similarity index 58% rename from pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/project_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json index 0405524b40..c281ce3c79 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/project_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json @@ -5,9 +5,8 @@ { "type": "schema", "children": [ - "test_project_schema", - "ftrack_projects_gui_schema", - "plugins_gui_schema" + "1_ftrack_projects_gui_schema", + "1_plugins_gui_schema" ] } ] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/ftrack_projects_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 1eaecbb8e5..1d1f299b55 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -1,23 +1,23 @@ { - "key": "plugins", "type": "dict-expanding", + "key": "plugins", "label": "Plugins", "children": [ { - "key": "maya", "type": "dict-expanding", + "key": "maya", "label": "Maya", "children": [ { - "key": "publish", "type": "dict-expanding", + "key": "publish", "label": "Publish plugins", "is_group": true, "is_file": true, "children": [ { - "key": "ValidateModelName", "type": "dict-invisible", + "key": "ValidateModelName", "label": "Validate Model Name", "children": [ { @@ -27,8 +27,8 @@ } ] }, { - "key": "ValidateAssemblyName", "type": "dict-invisible", + "key": "ValidateAssemblyName", "label": "Validate Assembly Name", "children": [ { diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/test_project_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/test_project_schema.json deleted file mode 100644 index 43c9a647f4..0000000000 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/test_project_schema.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "key": "test_inputs", - "type": "dict-expanding", - "label": "Test inputs", - "is_group": true, - "is_file": true, - "children": [ - { - "key": "boolean", - "type": "boolean", - "label": "Boolean" - }, { - "key": "test_singleline", - "type": "text-singleline", - "label": "Text singleline" - }, { - "key": "test_multiline", - "type": "text-multiline", - "label": "Text multiline" - }, { - "key": "raw_json", - "type": "raw-json", - "label": "Raw json" - }, { - "key": "int", - "type": "int", - "label": "Int" - }, { - "key": "float", - "type": "float", - "label": "Float" - } - ] -} diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 23eabdb531..7bbd95b135 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -308,7 +308,7 @@ class ProjectWidget(QtWidgets.QWidget): def reset(self): values = config.global_project_configurations() - schema = lib.gui_schema("projects_schema", "project_gui_schema") + schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) self.schema = schema From 332fe3f668b9eef7251539ae47d9b40b2d628b78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 14:58:24 +0200 Subject: [PATCH 489/947] DictExpandWidget inherits ExpandingWidget --- .../config_setting/widgets/inputs.py | 60 ++----------------- 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3a862b7a3e..9bcc54655b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1442,7 +1442,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): # Dictionaries -class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): +class DictExpandWidget(ExpandingWidget, ConfigWidget): value_changed = QtCore.Signal(object) def __init__( @@ -1469,31 +1469,8 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): self._state = None self._child_state = None - super(DictExpandWidget, self).__init__(parent) + super(DictExpandWidget, self).__init__(input_data["label"], parent) self.setObjectName("DictExpandWidget") - top_part = ClickableWidget(parent=self) - - button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) - button_toggle.setProperty("btn-type", "expand-toggle") - button_toggle.setIconSize(button_size) - button_toggle.setArrowType(QtCore.Qt.RightArrow) - button_toggle.setCheckable(True) - button_toggle.setChecked(False) - - label = input_data["label"] - button_toggle_text = QtWidgets.QLabel(label, parent=top_part) - button_toggle_text.setObjectName("ExpandLabel") - - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(button_toggle_text) - top_part.setLayout(layout) - - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 9) content_widget = QtWidgets.QWidget(self) content_widget.setVisible(False) @@ -1501,22 +1478,13 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 3, 3) - main_layout.addWidget(top_part) - main_layout.addWidget(content_widget) - self.setLayout(main_layout) + self.set_content_widget(content_widget) self.setAttribute(QtCore.Qt.WA_StyledBackground) - self.top_part = top_part - self.button_toggle = button_toggle - self.button_toggle_text = button_toggle_text - self.content_widget = content_widget self.content_layout = content_layout - self.top_part.clicked.connect(self._top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) - self.input_fields = [] self.key = input_data["key"] @@ -1527,26 +1495,6 @@ class DictExpandWidget(QtWidgets.QWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) - def _top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) - - def toggle_content(self, *args): - if len(args) > 0: - checked = args[0] - else: - checked = self.button_toggle.isChecked() - arrow_type = QtCore.Qt.RightArrow - if checked: - arrow_type = QtCore.Qt.DownArrow - self.button_toggle.setChecked(checked) - self.button_toggle.setArrowType(arrow_type) - self.content_widget.setVisible(checked) - self.parent().updateGeometry() - - def resizeEvent(self, event): - super(DictExpandWidget, self).resizeEvent(event) - self.content_widget.updateGeometry() - def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -1901,8 +1849,8 @@ TypeToKlass.types["text-multiline"] = TextMultiLineWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget TypeToKlass.types["float"] = FloatWidget +TypeToKlass.types["dict-modifiable"] = ModifiableDict TypeToKlass.types["dict-expanding"] = DictExpandWidget TypeToKlass.types["dict-form"] = DictFormWidget TypeToKlass.types["dict-invisible"] = DictInvisible -TypeToKlass.types["dict-modifiable"] = ModifiableDict TypeToKlass.types["list-text"] = TextListWidget From 93c30ed32049ecd8eb4b117b634049d6656ed8fe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 14:59:49 +0200 Subject: [PATCH 490/947] added expanding policy of default input labels --- .../config_setting/widgets/inputs.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9bcc54655b..0497cb7763 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -151,9 +151,9 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): label = input_data["label"] label_widget = QtWidgets.QLabel(label) label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - layout.addWidget(label_widget) + layout.addWidget(label_widget, 0) - layout.addWidget(self.checkbox) + layout.addWidget(self.checkbox, 1) if not self._as_widget: self.label_widget = label_widget @@ -279,8 +279,8 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.int_input) + layout.addWidget(label_widget, 0) + layout.addWidget(self.int_input, 1) if not self._as_widget: self.label_widget = label_widget @@ -403,8 +403,8 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.float_input) + layout.addWidget(label_widget, 0) + layout.addWidget(self.float_input, 1) if not self._as_widget: self.label_widget = label_widget @@ -515,8 +515,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) + layout.addWidget(label_widget, 0) + layout.addWidget(self.text_input, 1) if not self._as_widget: self.label_widget = label_widget @@ -628,8 +628,8 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) + layout.addWidget(label_widget, 0) + layout.addWidget(self.text_input, 1) self.label_widget = label_widget From f0c7dedb4d021860a0991a07e16b3c3814e11c5e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 16:02:31 +0200 Subject: [PATCH 491/947] adde nonexpanding widget --- .../config_setting/widgets/inputs.py | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0497cb7763..0ceec27d67 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1553,6 +1553,132 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): self.button_toggle_text.setProperty("state", state) self.button_toggle_text.style().polish(self.button_toggle_text) +class DictWidget(QtWidgets.QWidget, ConfigWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + if values is AS_WIDGET: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + self._parent = parent + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + is_group = input_data.get("is_group", False) + if is_group and any_parent_is_group: + raise SchemeGroupHierarchyBug() + + self.any_parent_is_group = any_parent_is_group + + self.is_group = is_group + + self._state = None + self._child_state = None + + super(DictWidget, self).__init__(parent) + self.setObjectName("DictWidget") + + body_widget = QtWidgets.QWidget(self) + + label_widget = QtWidgets.QLabel( + input_data["label"], parent=body_widget + ) + label_widget.setObjectName("ExpandLabel") + + content_widget = QtWidgets.QWidget(body_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + + body_layout = QtWidgets.QVBoxLayout(body_widget) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(5) + body_layout.addWidget(label_widget) + body_layout.addWidget(content_widget) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(5, 5, 5, 5) + main_layout.setSpacing(0) + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.content_widget = content_widget + self.content_layout = content_layout + + self.input_fields = [] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def apply_overrides(self, override_value): + # Make sure this is set to False + self._is_overriden = False + self._state = None + self._child_state = None + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + # TODO update items + if item is not None: + for _item in self.input_fields: + if _item is not item: + _item.update_style() + + self.value_changed.emit(self) + + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_state = self.style_state(self.child_overriden, child_modified) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) self._state = state @@ -1850,6 +1976,7 @@ TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget TypeToKlass.types["float"] = FloatWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict +TypeToKlass.types["dict"] = DictWidget TypeToKlass.types["dict-expanding"] = DictExpandWidget TypeToKlass.types["dict-form"] = DictFormWidget TypeToKlass.types["dict-invisible"] = DictInvisible From 8c6ff3b7f825b54a2ed23cc6b52707921cf13d8f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 16:04:04 +0200 Subject: [PATCH 492/947] fixed dict expanding --- .../config_setting/widgets/inputs.py | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0ceec27d67..61c6385323 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1551,8 +1551,69 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): if self._state == state: return - self.button_toggle_text.setProperty("state", state) - self.button_toggle_text.style().polish(self.button_toggle_text) + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + @property + def is_modified(self): + if self.is_group: + return self.child_modified + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + item.value_changed.connect(self._on_value_change) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + class DictWidget(QtWidgets.QWidget, ConfigWidget): value_changed = QtCore.Signal(object) From 4776eaca79c5c3b62c793dfcadbce51d33d18172 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 16:17:34 +0200 Subject: [PATCH 493/947] Added few plugins --- .../projects_schema/1_plugins_gui_schema.json | 129 +++++++++++++++++- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 1d1f299b55..e2717bca37 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -4,6 +4,21 @@ "label": "Plugins", "children": [ { + "type": "dict-expanding", + "key": "celaction", + "label": "CelAction", + "children": [] + }, { + "type": "dict-expanding", + "key": "ftrack", + "label": "Ftrack", + "children": [] + }, { + "type": "dict-expanding", + "key": "global", + "label": "Global", + "children": [] + }, { "type": "dict-expanding", "key": "maya", "label": "Maya", @@ -12,28 +27,29 @@ "type": "dict-expanding", "key": "publish", "label": "Publish plugins", - "is_group": true, "is_file": true, "children": [ { - "type": "dict-invisible", + "type": "dict", "key": "ValidateModelName", "label": "Validate Model Name", + "is_group": true, "children": [ { - "key": "enabled", "type": "boolean", + "key": "enabled", "label": "Enabled" } ] }, { - "type": "dict-invisible", + "type": "dict", "key": "ValidateAssemblyName", "label": "Validate Assembly Name", + "is_group": true, "children": [ { - "key": "enabled", "type": "boolean", + "key": "enabled", "label": "Enabled" } ] @@ -41,6 +57,109 @@ ] } ] + }, { + "type": "dict-expanding", + "key": "nuke", + "label": "Nuke", + "children": [] + }, { + "type": "dict-expanding", + "key": "nukestudio", + "label": "NukeStudio", + "children": [ + { + "type": "dict-expanding", + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "key": "CollectInstanceVersion", + "label": "Collect Instance Version", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": false + } + ] + }, { + "type": "dict", + "key": "ExtractReviewCutUpVideo", + "label": "Extract Review Cut Up Video", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": true + }, { + "type": "list-text", + "key": "tags_addition", + "label": "Tags addition", + "default": [] + } + ] + } + ] + } + ] + }, { + "type": "dict-expanding", + "key": "resolve", + "label": "DaVinci Resolve", + "children": [ + { + "type": "dict-expanding", + "key": "create", + "label": "Creator plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [ + { + "type": "text-singleline", + "key": "clipName", + "label": "Clip name template", + "default": "{track}{sequence}{shot}" + }, { + "type": "text-singleline", + "key": "folder", + "label": "Folder", + "default": "takes" + }, { + "type": "text-singleline", + "key": "steps", + "label": "Steps", + "default": 20 + } + ] + } + + ] + } + ] + }, { + "type": "dict-expanding", + "key": "standalonepublisher", + "label": "StandalonePublisher", + "children": [ + { + "type": "dict-expanding", + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [] + } + ] } ] } From 044c644c3bcd871af62bd6932e27d8341d3363ff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 16:21:11 +0200 Subject: [PATCH 494/947] project_presets changed to project_configurations --- pype/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/config.py b/pype/config.py index 454835e005..c65e336e00 100644 --- a/pype/config.py +++ b/pype/config.py @@ -8,7 +8,7 @@ log = logging.getLogger(__name__) STUDIO_PRESETS_PATH = os.path.normpath( os.path.join(os.environ["PYPE_CONFIG"], "studio_configurations") ) -PROJECT_CONFIGURATION_DIR = "project_presets" +PROJECT_CONFIGURATION_DIR = "project_configurations" PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( os.environ["PYPE_CONFIG"], PROJECT_CONFIGURATION_DIR )) From f0217db5da872ffd07b0ea8919b86d15e230df49 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 16:52:04 +0200 Subject: [PATCH 495/947] fixed overrides --- pype/tools/config_setting/config_setting/widgets/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 7bbd95b135..a59b497b2b 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -329,7 +329,7 @@ class ProjectWidget(QtWidgets.QWidget): overrides = None self.is_overidable = False else: - overrides = config.project_preset_overrides(project_name) + overrides = config.project_configurations_overrides(project_name) self.is_overidable = True self.project_name = project_name From 7134b5b9fc2bbb61803c13ba2236d450b974ac78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 16:52:14 +0200 Subject: [PATCH 496/947] added few plugins --- .../projects_schema/1_plugins_gui_schema.json | 187 +++++++++++++++++- 1 file changed, 183 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index e2717bca37..c12825c3ec 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -7,17 +7,41 @@ "type": "dict-expanding", "key": "celaction", "label": "CelAction", - "children": [] + "children": [ + { + "type": "dict-expanding", + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [] + } + ] }, { "type": "dict-expanding", "key": "ftrack", "label": "Ftrack", - "children": [] + "children": [ + { + "type": "dict-expanding", + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [] + } + ] }, { "type": "dict-expanding", "key": "global", "label": "Global", - "children": [] + "children": [ + { + "type": "dict-expanding", + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [] + } + ] }, { "type": "dict-expanding", "key": "maya", @@ -61,7 +85,162 @@ "type": "dict-expanding", "key": "nuke", "label": "Nuke", - "children": [] + "children": [ + { + "type": "dict-expanding", + "key": "create", + "label": "Create plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "key": "CreateWriteRender", + "label": "CreateWriteRender", + "is_group": true, + "children": [ + { + "type": "text-singleline", + "key": "fpath_template", + "label": "Path template", + "default": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + } + ] + }, { + "type": "dict", + "key": "CreateWritePrerender", + "label": "CreateWritePrerender", + "is_group": true, + "children": [ + { + "type": "text-singleline", + "key": "fpath_template", + "label": "Path template", + "default": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + } + ] + } + ] + }, { + "type": "dict-expanding", + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": false + }, { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + } + ] + }, { + "type": "dict", + "key": "ValidateNukeWriteKnobs", + "label": "ValidateNukeWriteKnobs", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": false + }, { + "type": "raw-json", + "key": "knobs", + "label": "Knobs" + } + ] + }, { + "type": "dict", + "key": "ExtractReviewDataLut", + "label": "ExtractReviewDataLut", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": false + } + ] + }, { + "type": "dict", + "key": "ExtractReviewDataMov", + "label": "ExtractReviewDataMov", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": true + }, { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw", + "default": false + } + ] + }, { + "type": "dict", + "key": "ExtractSlateFrame", + "label": "ExtractSlateFrame", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw", + "default": false + } + ] + }, { + "type": "dict", + "key": "NukeSubmitDeadline", + "label": "NukeSubmitDeadline", + "is_group": true, + "children": [ + { + "type": "int", + "key": "deadline_priority", + "label": "deadline_priority", + "default": 50 + }, { + "type": "text-singleline", + "key": "deadline_pool", + "label": "deadline_pool", + "default": "" + }, { + "type": "text-singleline", + "key": "deadline_pool_secondary", + "label": "deadline_pool_secondary", + "default": "" + }, { + "type": "int", + "key": "deadline_chunk_size", + "label": "deadline_chunk_size", + "default": 1 + } + ] + } + ] + }, { + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true + } + ] }, { "type": "dict-expanding", "key": "nukestudio", From 1dea23f1cc407cbb1eb78c90c41e36f30f29e240 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 17:09:42 +0200 Subject: [PATCH 497/947] added workfile build to maya plugins --- .../projects_schema/1_plugins_gui_schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index c12825c3ec..4b6583dd63 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -79,6 +79,11 @@ ] } ] + }, { + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true } ] }, { From dbd50e0ad24e76f9c15a429f43620f0439c37031 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 18:59:51 +0200 Subject: [PATCH 498/947] added global presets --- .../projects_schema/1_plugins_gui_schema.json | 218 +++++++++++++++++- 1 file changed, 215 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 4b6583dd63..458695da18 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -13,7 +13,52 @@ "key": "publish", "label": "Publish plugins", "is_file": true, - "children": [] + "children": [ + { + "type": "dict", + "key": "ExtractCelactionDeadline", + "label": "ExtractCelactionDeadline", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": true + }, { + "type": "text-singleline", + "key": "deadline_department", + "label": "Deadline apartment", + "default": "" + }, { + "type": "int", + "key": "deadline_priority", + "label": "Deadline priority", + "default": 50 + }, { + "type": "text-singleline", + "key": "deadline_pool", + "label": "Deadline pool", + "default": "" + }, { + "type": "text-singleline", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)", + "default": "" + }, { + "type": "text-singleline", + "key": "deadline_group", + "label": "Deadline Group", + "default": "" + }, { + "type": "int", + "key": "deadline_chunk_size", + "label": "Deadline Chunk size", + "default": 10 + } + ] + } + ] } ] }, { @@ -26,7 +71,31 @@ "key": "publish", "label": "Publish plugins", "is_file": true, - "children": [] + "children": [ + { + "type": "dict", + "key": "IntegrateFtrackNote", + "label": "IntegrateFtrackNote", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text-singleline", + "key": "note_with_intent_template", + "label": "Note with intent template", + "default": "{intent}: {comment}" + }, { + "type": "list-text", + "key": "note_labels", + "label": "Note labels", + "default": [] + } + ] + } + ] } ] }, { @@ -39,7 +108,150 @@ "key": "publish", "label": "Publish plugins", "is_file": true, - "children": [] + "children": [ + { + "type": "dict", + "key": "IntegrateMasterVersion", + "label": "IntegrateMasterVersion", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "key": "ExtractJpegEXR", + "label": "ExtractJpegEXR", + "is_group": true, + "children": [ + { + "type": "dict-invisible", + "key": "ffmpeg_args", + "children": [ + { + "type": "list-text", + "key": "input", + "label": "FFmpeg input arguments", + "default": [] + }, { + "type": "list-text", + "key": "output", + "label": "FFmpeg output arguments", + "default": [] + } + ] + } + ] + }, { + "type": "dict", + "key": "ExtractReview", + "label": "ExtractReview", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "profiles", + "label": "Profiles" + } + ] + }, { + "type": "dict", + "key": "ExtractBurnin", + "label": "ExtractBurnin", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-expanding", + "key": "options", + "label": "Burnin formating options", + "children": [ + { + "type": "int", + "key": "font_size", + "label": "Font size", + "default": 42 + }, { + "type": "int", + "key": "opacity", + "label": "Font opacity", + "default": 1 + }, { + "type": "int", + "key": "bg_opacity", + "label": "Background opacity", + "default": 1 + }, { + "type": "int", + "key": "x_offset", + "label": "X Offset", + "default": 5 + }, { + "type": "int", + "key": "y_offset", + "label": "Y Offset", + "default": 5 + }, { + "type": "int", + "key": "bg_padding", + "label": "Padding aroung text", + "default": 5 + } + ] + }, { + "type": "raw-json", + "key": "profiles", + "label": "Burnin profiles" + } + ] + }, { + "type": "dict", + "key": "IntegrateAssetNew", + "label": "IntegrateAssetNew", + "is_group": true, + "children": [ + { + "type": "raw-json", + "key": "template_name_profiles", + "label": "template_name_profiles" + } + ] + }, { + "type": "dict", + "key": "ProcessSubmittedJobOnFarm", + "label": "ProcessSubmittedJobOnFarm", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text-singleline", + "key": "deadline_department", + "label": "Deadline department" + }, { + "type": "text-singleline", + "key": "deadline_pool", + "label": "Deadline Pool" + }, { + "type": "text-singleline", + "key": "deadline_group", + "label": "Deadline Group" + } + ] + } + ] } ] }, { From 4301c5dcdcfee138deee8544c065b132113a3a89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 19:04:06 +0200 Subject: [PATCH 499/947] updated maya plugins --- .../projects_schema/1_plugins_gui_schema.json | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 458695da18..2b4dfafc56 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -275,6 +275,15 @@ "type": "boolean", "key": "enabled", "label": "Enabled" + }, { + "type": "text-singleline", + "key": "material_file", + "label": "Material File" + }, { + "type": "text-singleline", + "key": "regex", + "label": "Validation regex", + "default": "(.*)_(\\d)*_(?P.*)_(GEO)" } ] }, { @@ -289,6 +298,35 @@ "label": "Enabled" } ] + }, { + "type": "dict", + "key": "ValidateShaderName", + "label": "ValidateShaderName", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text-singleline", + "key": "regex", + "label": "Validation regex", + "default": "(?P.*)_(.*)_SHD" + } + ] + }, { + "type": "dict", + "key": "ValidateMeshHasOverlappingUVs", + "label": "ValidateMeshHasOverlappingUVs", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] } ] }, { From 5bdee32e7ef7d7fec0909aad23a9703f8e82679e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 19:12:46 +0200 Subject: [PATCH 500/947] few minor fixes --- .../config_setting/config_setting/widgets/base.py | 3 +-- .../config_setting/config_setting/widgets/inputs.py | 10 +--------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index a59b497b2b..74e0543870 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -307,7 +307,7 @@ class ProjectWidget(QtWidgets.QWidget): self.reset() def reset(self): - values = config.global_project_configurations() + values = {"project": config.global_project_configurations()} schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = schema.get("keys", []) self.add_children_gui(schema, values) @@ -316,7 +316,6 @@ class ProjectWidget(QtWidgets.QWidget): def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] klass = lib.TypeToKlass.types.get(item_type) - item = klass( child_configuration, values, self.keys, self ) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 61c6385323..0f1ef844d3 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1832,9 +1832,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): self.input_fields = [] - if "key" not in input_data: - print(json.dumps(input_data, indent=4)) - self.key = input_data["key"] self.keys = list(parent_keys) self.keys.append(self.key) @@ -1869,13 +1866,8 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] - if item_type == "schema": - for _schema in child_configuration["children"]: - children = config.gui_schema(_schema) - self.add_children_gui(children, values) - return - klass = TypeToKlass.types.get(item_type) + item = klass( child_configuration, values, self.keys, self ) From a575ed37291fe80ce448f99085f0c51760ee0724 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 19:17:06 +0200 Subject: [PATCH 501/947] minor fix in raw json --- .../projects_schema/1_plugins_gui_schema.json | 2 +- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 2b4dfafc56..e6582d82b1 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -570,7 +570,7 @@ "label": "Folder", "default": "takes" }, { - "type": "text-singleline", + "type": "int", "key": "steps", "label": "Steps", "default": 20 diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0f1ef844d3..6100087ac7 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -719,6 +719,8 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.is_valid = None def set_value(self, value, *, default_value=False): + if not isinstance(value, str): + value = json.dumps(value, indent=4) self.setPlainText(value) def setPlainText(self, *args, **kwargs): @@ -808,7 +810,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): value = self.value_from_values(values) if value is not NOT_SET: - self.text_input.setPlainText(value) + self.text_input.set_value(value) self.default_value = self.item_value() self.override_value = None @@ -816,7 +818,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.text_input.textChanged.connect(self._on_value_change) def set_value(self, value, *, default_value=False): - self.text_input.setPlainText(value) + self.text_input.set_value(value) if default_value: self.default_value = self.item_value() self._on_value_change() From 49a4e73fc65193c73d430d87409b641ac266dd7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Aug 2020 19:26:01 +0200 Subject: [PATCH 502/947] removed ftrack overrides as not ready yet --- .../config_gui_schema/projects_schema/0_project_gui_schema.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json index c281ce3c79..10641d5eda 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json @@ -5,7 +5,6 @@ { "type": "schema", "children": [ - "1_ftrack_projects_gui_schema", "1_plugins_gui_schema" ] } From b648b3c72e1199a3f282f8030d9d5c47cfb35681 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 25 Aug 2020 08:35:09 +0100 Subject: [PATCH 503/947] Add "preview" to image plane representations --- pype/plugins/maya/load/load_image_plane.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index 08f7c99156..17a6866f80 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -12,7 +12,7 @@ class ImagePlaneLoader(api.Loader): families = ["plate", "render"] label = "Create imagePlane on selected camera." - representations = ["mov", "exr"] + representations = ["mov", "exr", "preview"] icon = "image" color = "orange" @@ -83,7 +83,8 @@ class ImagePlaneLoader(api.Loader): image_plane_shape.frameOut.set(end_frame) image_plane_shape.useFrameExtension.set(1) - if context["representation"]["name"] == "mov": + movie_representations = ["mov", "preview"] + if context["representation"]["name"] in movie_representations: # Need to get "type" by string, because its a method as well. pc.Attribute(image_plane_shape + ".type").set(2) From e35ef732b84f8d7e9603dc50c7aca70e3b957920 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 10:49:02 +0200 Subject: [PATCH 504/947] fixed content margins on right side --- .../config_setting/config_setting/widgets/inputs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 6100087ac7..2cb1be3c63 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -932,7 +932,7 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): self.setObjectName("TextListSubWidget") layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 5, 5) + layout.setContentsMargins(5, 5, 0, 5) layout.setSpacing(5) self.setLayout(layout) @@ -1251,7 +1251,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): self.setObjectName("ModifiableDictSubWidget") layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 5, 5) + layout.setContentsMargins(5, 5, 0, 5) layout.setSpacing(5) self.setLayout(layout) @@ -1478,7 +1478,7 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): content_widget.setVisible(False) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setContentsMargins(3, 3, 0, 3) self.set_content_widget(content_widget) @@ -1655,7 +1655,7 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): content_widget = QtWidgets.QWidget(body_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setContentsMargins(3, 3, 0, 3) body_layout = QtWidgets.QVBoxLayout(body_widget) body_layout.setContentsMargins(0, 0, 0, 0) @@ -1666,7 +1666,7 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): self.setAttribute(QtCore.Qt.WA_StyledBackground) main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(5, 5, 5, 5) + main_layout.setContentsMargins(5, 5, 0, 5) main_layout.setSpacing(0) main_layout.addWidget(body_widget) From 7cd5b58b653b51379d675867c69a428a2a8b2194 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 10:50:42 +0200 Subject: [PATCH 505/947] modified style --- .../config_setting/style/style.css | 34 +++++++++---------- .../config_setting/widgets/inputs.py | 3 +- .../config_setting/widgets/widgets.py | 4 +-- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index a0c5db86a1..9bd74de282 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -2,6 +2,7 @@ QWidget { color: #bfccd6; background-color: #293742; font-size: 12px; + border-radius: 0px; } QCheckBox::indicator { @@ -58,44 +59,43 @@ QPushButton[btn-type="expand-toggle"] { } #RawJsonInput[state="invalid"] { - border-color: #ff5511; + border-left-color: #ff5511; } #DictKey[state="modified"] { - border-color: #137cbd; + border-left-color: #137cbd; } #DictKey[state="overriden"] { - border-color: #00f; + border-left-color: #00f; } #DictKey[state="overriden-modified"] { - border-color: #0f0; + border-left-color: #0f0; } -#ExpandLabel { +#DictLabel { background: transparent; + font-weight: bold; } -#DictExpandWidget, #ModifiableDict, #ExpandingWidget { - border: 1px solid #455c6e; - border-radius: 3px; +#ExpandingWidget, #ModifiableDict, #DictWidget { + border-left: 3px solid #455c6e; background: #1d272f; } -#DictExpandWidget[state="child-modified"], #ModifiableDict[state="child-modified"] { - border-color: #137cbd; +#ExpandingWidget[state="child-modified"], #ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { + border-left-color: #137cbd; } -#DictExpandWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"] { - border-color: #ff8c1a; +#ExpandingWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"] { + border-left-color: #ff8c1a; } -#DictExpandWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"] { - border-color: #00b386; +#ExpandingWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"] { + border-left-color: #00b386; } #TextListSubWidget { - border: 1px solid #455c6e; - border-radius: 3px; - background: #1d272f; + border-left: 3px solid #455c6e; + /* background: #1d272f; */ } diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 2cb1be3c63..9023d36826 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1472,7 +1472,6 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): self._child_state = None super(DictExpandWidget, self).__init__(input_data["label"], parent) - self.setObjectName("DictExpandWidget") content_widget = QtWidgets.QWidget(self) content_widget.setVisible(False) @@ -1651,7 +1650,7 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): label_widget = QtWidgets.QLabel( input_data["label"], parent=body_widget ) - label_widget.setObjectName("ExpandLabel") + label_widget.setObjectName("DictLabel") content_widget = QtWidgets.QWidget(body_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 3d5528e17a..a15edf58ff 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -54,7 +54,7 @@ class ExpandingWidget(QtWidgets.QWidget): button_toggle.setChecked(False) label_widget = QtWidgets.QLabel(label, parent=top_part) - label_widget.setObjectName("ExpandLabel") + label_widget.setObjectName("DictLabel") layout = QtWidgets.QHBoxLayout(top_part) layout.setContentsMargins(0, 0, 0, 0) @@ -74,7 +74,7 @@ class ExpandingWidget(QtWidgets.QWidget): def set_content_widget(self, content_widget): main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 9, 9) + main_layout.setContentsMargins(9, 9, 0, 9) content_widget.setVisible(False) From f4cdb85c14ef49987d4e154d28d567fbaeba687e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 10:57:48 +0200 Subject: [PATCH 506/947] fixed project list styles --- .../config_setting/config_setting/style/style.css | 12 ++++++++++-- .../config_setting/config_setting/widgets/base.py | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 9bd74de282..2ea765f442 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -56,6 +56,16 @@ QPushButton[btn-type="text-list"]:hover { QPushButton[btn-type="expand-toggle"] { background: transparent; + background: #1d272f; +} + +#ProjectListWidget QListView { + border: 1px solid #aaaaaa; + background: #1d272f; +} +#ProjectListWidget QLabel { + background: transparent; + font-weight: bold; } #RawJsonInput[state="invalid"] { @@ -80,7 +90,6 @@ QPushButton[btn-type="expand-toggle"] { #ExpandingWidget, #ModifiableDict, #DictWidget { border-left: 3px solid #455c6e; - background: #1d272f; } #ExpandingWidget[state="child-modified"], #ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { @@ -97,5 +106,4 @@ QPushButton[btn-type="expand-toggle"] { #TextListSubWidget { border-left: 3px solid #455c6e; - /* background: #1d272f; */ } diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 74e0543870..5f48b416c1 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -140,8 +140,10 @@ class ProjectListWidget(QtWidgets.QWidget): self.current_project = None super(ProjectListWidget, self).__init__(parent) + self.setObjectName("ProjectListWidget") label_widget = QtWidgets.QLabel("Projects") + project_list = ProjectListView(self) project_list.setModel(QtGui.QStandardItemModel()) @@ -282,7 +284,7 @@ class ProjectWidget(QtWidgets.QWidget): configurations_widget = QtWidgets.QWidget() configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) - configurations_layout.setContentsMargins(0, 0, 0, 0) + configurations_layout.setContentsMargins(0, 0, 5, 0) configurations_layout.setSpacing(0) configurations_layout.addWidget(scroll_widget, 1) From 62ed17c597398fb6212ea48fd4fb230300d234b7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 10:58:11 +0200 Subject: [PATCH 507/947] removed unused imports --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9023d36826..0f106c6408 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1,8 +1,6 @@ import json from Qt import QtWidgets, QtCore, QtGui -from pype.api import config from .widgets import ( - ClickableWidget, ExpandingWidget, ModifiedIntSpinBox, ModifiedFloatSpinBox From e9551c7fc476e788f70b4b3c86371e6e0e8491ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 11:03:37 +0200 Subject: [PATCH 508/947] group widgets has borders --- pype/tools/config_setting/config_setting/style/style.css | 4 ++++ pype/tools/config_setting/config_setting/widgets/base.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 2ea765f442..aa1f6293d7 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -59,6 +59,10 @@ QPushButton[btn-type="expand-toggle"] { background: #1d272f; } +#GroupWidget { + border: 1px solid #aaaaaa; +} + #ProjectListWidget QListView { border: 1px solid #aaaaaa; background: #1d272f; diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 5f48b416c1..4fe3866096 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -20,6 +20,7 @@ class StudioWidget(QtWidgets.QWidget): self.input_fields = [] scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setObjectName("GroupWidget") content_widget = QtWidgets.QWidget(scroll_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 3, 3) @@ -43,7 +44,7 @@ class StudioWidget(QtWidgets.QWidget): footer_layout.addWidget(save_btn, 0) layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) + layout.setContentsMargins(5, 0, 5, 0) layout.setSpacing(0) self.setLayout(layout) @@ -261,6 +262,7 @@ class ProjectWidget(QtWidgets.QWidget): self.input_fields = [] scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setObjectName("GroupWidget") content_widget = QtWidgets.QWidget(scroll_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 3, 3) @@ -284,7 +286,7 @@ class ProjectWidget(QtWidgets.QWidget): configurations_widget = QtWidgets.QWidget() configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) - configurations_layout.setContentsMargins(0, 0, 5, 0) + configurations_layout.setContentsMargins(5, 0, 5, 0) configurations_layout.setSpacing(0) configurations_layout.addWidget(scroll_widget, 1) From b51e24318efb2dcb1bc0a3145877fed73bafda69 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 11:14:17 +0200 Subject: [PATCH 509/947] overriden-modified has same color as modified --- .../config_setting/style/style.css | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index aa1f6293d7..961bfa0d03 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -25,7 +25,7 @@ QLabel[state="modified"] { } QLabel[state="overriden-modified"] { - color: #00b386; + color: #137cbd; } QLabel[state="overriden"] { @@ -37,13 +37,18 @@ QWidget[input-state="modified"] { } QWidget[input-state="overriden-modified"] { - border-color: #00b386; + border-color: #137cbd; } QWidget[input-state="overriden"] { border-color: #ff8c1a; } +QPushButton { + border: 1px solid #aaaaaa; + border-radius: 3px; + padding: 5px; +} QPushButton[btn-type="text-list"] { border: 1px solid #bfccd6; border-radius: 10px; @@ -93,19 +98,24 @@ QPushButton[btn-type="expand-toggle"] { } #ExpandingWidget, #ModifiableDict, #DictWidget { - border-left: 3px solid #455c6e; + border-style: solid; + border-color: #455c6e; + border-left-width: 3px; + border-bottom-width: 0px; + border-right-width: 0px; + border-top-width: 0px; } #ExpandingWidget[state="child-modified"], #ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { - border-left-color: #137cbd; + border-color: #137cbd; } #ExpandingWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"] { - border-left-color: #ff8c1a; + border-color: #ff8c1a; } #ExpandingWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"] { - border-left-color: #00b386; + border-color: #137cbd; } #TextListSubWidget { From 43d2a70d0d10a7872fd8d86ce854ec5dde96ac8c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 11:32:21 +0200 Subject: [PATCH 510/947] inputs has different background --- pype/tools/config_setting/config_setting/style/style.css | 1 + pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 961bfa0d03..b040425a02 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -14,6 +14,7 @@ QCheckBox::indicator:focus { QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit { border: 1px solid #aaaaaa; border-radius: 3px; + background: #1d272f; } QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus { diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0f106c6408..c64e0e43cc 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -796,8 +796,8 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - layout.addWidget(self.text_input) + layout.addWidget(label_widget, 0) + layout.addWidget(self.text_input, 1) self.label_widget = label_widget From 34416f46ddeeb16ec44dee7873341a14da9e38ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 13:20:07 +0200 Subject: [PATCH 511/947] adde more transparency --- .../config_setting/config_setting/style/style.css | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index b040425a02..e7224533da 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -11,15 +11,18 @@ QCheckBox::indicator:focus { color: #ff0000; } -QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit { +QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit { border: 1px solid #aaaaaa; border-radius: 3px; - background: #1d272f; + background-color: #1d272f; } -QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus { +QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus { border: 1px solid #ffffff; } +QLabel, QToolButton { + background: transparent; +} QLabel[state="modified"] { color: #137cbd; @@ -61,7 +64,6 @@ QPushButton[btn-type="text-list"]:hover { } QPushButton[btn-type="expand-toggle"] { - background: transparent; background: #1d272f; } @@ -94,7 +96,6 @@ QPushButton[btn-type="expand-toggle"] { } #DictLabel { - background: transparent; font-weight: bold; } From 1390637bb1ce6949935c97b862eb72a011e4f822 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 13:21:23 +0200 Subject: [PATCH 512/947] tried to extend raw json input --- .../config_setting/config_setting/widgets/inputs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c64e0e43cc..4e63a4a501 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -752,6 +752,11 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.setProperty("state", state) self.style().polish(self) + def resizeEvent(self, event): + self.updateGeometry() + super().resizeEvent(event) + + def value(self): return self.toPlainText() @@ -791,7 +796,11 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) - self.text_input = RawJsonInput() + self.text_input = RawJsonInput(self) + self.text_input.setSizePolicy( + QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.MinimumExpanding + ) if not label_widget: label = input_data["label"] From cdc407f7ad341132d1b4b1d55809901b37cfa60f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 13:24:48 +0200 Subject: [PATCH 513/947] Hound fixes --- pype/config.py | 4 +++- pype/tools/config_setting/config_setting/widgets/inputs.py | 1 - pype/tools/config_setting/config_setting/widgets/tests.py | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pype/config.py b/pype/config.py index c65e336e00..416704649c 100644 --- a/pype/config.py +++ b/pype/config.py @@ -188,6 +188,8 @@ def project_presets(project_name=None, **kwargs): if not project_name: project_name = os.environ.get("AVALON_PROJECT") - project_overrides = project_configurations_overrides(project_name, **kwargs) + project_overrides = project_configurations_overrides( + project_name, **kwargs + ) return apply_overrides(global_presets, project_overrides) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4e63a4a501..19286a19b5 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -756,7 +756,6 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.updateGeometry() super().resizeEvent(event) - def value(self): return self.toPlainText() diff --git a/pype/tools/config_setting/config_setting/widgets/tests.py b/pype/tools/config_setting/config_setting/widgets/tests.py index 53b67de3a1..16c97a85ef 100644 --- a/pype/tools/config_setting/config_setting/widgets/tests.py +++ b/pype/tools/config_setting/config_setting/widgets/tests.py @@ -63,8 +63,6 @@ class AddibleComboBox(QtWidgets.QComboBox): # self.addItems([text]) # index = self.findText(text) - - def populate(self, items): self.clear() # self.addItems([""]) # ensure first item is placeholder From 6d31920609220ad8e584a7d96f34a405314d660b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 13:31:05 +0200 Subject: [PATCH 514/947] added hovering on labels --- .../config_setting/style/style.css | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index e7224533da..af4808b443 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -24,17 +24,14 @@ QLabel, QToolButton { background: transparent; } -QLabel[state="modified"] { - color: #137cbd; -} +QLabel:hover {color: #ffffff;} -QLabel[state="overriden-modified"] { - color: #137cbd; -} - -QLabel[state="overriden"] { - color: #ff8c1a; -} +QLabel[state="modified"] {color: #137cbd;} +QLabel[state="modified"]:hover {color: #1798e8;} +QLabel[state="overriden-modified"] {color: #137cbd;} +QLabel[state="overriden-modified"]:hover {color: #1798e8;} +QLabel[state="overriden"] {color: #ff8c1a;} +QLabel[state="overriden"]:hover {color: #ffa64d;} QWidget[input-state="modified"] { border-color: #137cbd; From 422930ea8782a68f5ed008b9be55c1c72deed9aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 13:54:17 +0200 Subject: [PATCH 515/947] fixed raw json height sizes --- .../config_setting/widgets/inputs.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 19286a19b5..bd945f18e8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -716,6 +716,21 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.is_valid = None + def sizeHint(self): + document = self.document() + layout = document.documentLayout() + + height = document.documentMargin() + 2 * self.frameWidth() + 1 + block = document.begin() + while block != document.end(): + height += layout.blockBoundingRect(block).height() + block = block.next() + + value = super().sizeHint() + value.setHeight(height) + + return value + def set_value(self, value, *, default_value=False): if not isinstance(value, str): value = json.dumps(value, indent=4) From 1ac0c781338ab5734016cab12b8a3ae771fb3dfa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 14:16:21 +0200 Subject: [PATCH 516/947] text list widget does not have modification line next to it --- .../config_setting/config_setting/style/style.css | 4 ---- .../config_setting/config_setting/widgets/inputs.py | 10 +++++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index af4808b443..0099e6ed76 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -116,7 +116,3 @@ QPushButton[btn-type="expand-toggle"] { #ExpandingWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"] { border-color: #137cbd; } - -#TextListSubWidget { - border-left: 3px solid #455c6e; -} diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index bd945f18e8..0d07383d3c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -953,8 +953,8 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): self.setObjectName("TextListSubWidget") layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 0, 5) - layout.setSpacing(5) + layout.setContentsMargins(0, 5, 0, 5) + layout.setSpacing(3) self.setLayout(layout) self.input_fields = [] @@ -1088,9 +1088,9 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(label_widget) self.label_widget = label_widget - # keys = list(parent_keys) - # keys.append(input_data["key"]) - # self.keys = keys + keys = list(parent_keys) + keys.append(input_data["key"]) + self.keys = keys self.value_widget = TextListSubWidget( input_data, values, parent_keys, self From b7807f81f05697fc70a8a65a5cd2877a4720717e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 15:07:25 +0200 Subject: [PATCH 517/947] fix super --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0d07383d3c..e9104e6c0c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -726,7 +726,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): height += layout.blockBoundingRect(block).height() block = block.next() - value = super().sizeHint() + value = super(RawJsonInput, self).sizeHint() value.setHeight(height) return value @@ -769,7 +769,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): def resizeEvent(self, event): self.updateGeometry() - super().resizeEvent(event) + super(RawJsonInput, self).resizeEvent(event) def value(self): return self.toPlainText() From 6a585b082a744157addb859487cc2f06d27284b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 15:29:47 +0200 Subject: [PATCH 518/947] renamed default_value to global_value --- .../config_setting/widgets/inputs.py | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e9104e6c0c..140d67c0ad 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -164,18 +164,18 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.checkbox.setChecked(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None self.checkbox.stateChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): # Ignore value change because if `self.isChecked()` has same # value as `value` the `_on_value_change` is not triggered self.checkbox.setChecked(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() @@ -183,7 +183,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): if self.is_overidable and self.override_value is not None: self.set_value(self.override_value) else: - self.set_value(self.default_value) + self.set_value(self.global_value) def clear_value(self): self.reset_value() @@ -195,7 +195,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): if override_value is None: self._is_overriden = False self._was_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True self._was_overriden = True @@ -216,7 +216,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): is_modified = _value != self.override_value if is_modified is None: - is_modified = _value != self.default_value + is_modified = _value != self.global_value self._is_modified = is_modified @@ -292,22 +292,22 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.int_input.setValue(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None self.int_input.valueChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): self.int_input.setValue(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def clear_value(self): self.set_value(0) def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def apply_overrides(self, override_value): self._is_modified = False @@ -316,7 +316,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): if override_value is None: self._is_overriden = False self._was_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True self._was_overriden = True @@ -329,7 +329,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -416,19 +416,19 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.float_input.setValue(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None self.float_input.valueChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): self.float_input.setValue(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def apply_overrides(self, override_value): self._is_modified = False @@ -436,7 +436,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is None: self._is_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True value = override_value @@ -451,7 +451,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -528,19 +528,19 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.text_input.setText(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None self.text_input.textChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): self.text_input.setText(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def apply_overrides(self, override_value): self._is_modified = False @@ -549,7 +549,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): if override_value is None: self._is_overriden = False self._was_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True self._was_overriden = True @@ -565,7 +565,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -640,19 +640,19 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.text_input.setPlainText(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None self.text_input.textChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): self.text_input.setPlainText(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def apply_overrides(self, override_value): self._is_modified = False @@ -660,7 +660,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is None: self._is_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True value = override_value @@ -675,7 +675,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -731,7 +731,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): return value - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): if not isinstance(value, str): value = json.dumps(value, indent=4) self.setPlainText(value) @@ -833,19 +833,19 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.text_input.set_value(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None self.text_input.textChanged.connect(self._on_value_change) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): self.text_input.set_value(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def clear_value(self): self.set_value("") @@ -856,7 +856,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is None: self._is_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True value = override_value @@ -868,7 +868,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -969,22 +969,22 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): if value is not NOT_SET: self.set_value(value) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): for input_field in self.input_fields: self.remove_row(input_field) for item_text in value: self.add_row(text=item_text) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def clear_value(self): self.set_value([]) @@ -1103,13 +1103,13 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(self.value_widget) self.setLayout(layout) - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None def _on_value_change(self, item=None): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -1117,14 +1117,14 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self.value_changed.emit(self) - def set_value(self, value, *, default_value=False): + def set_value(self, value, *, global_value=False): self.value_widget.set_value(value) - if default_value: - self.default_value = self.item_value() + if global_value: + self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.default_value) + self.set_value(self.global_value) def clear_value(self): self.set_value([]) @@ -1135,7 +1135,7 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is None: self._is_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True value = override_value @@ -1200,7 +1200,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): self.value_input.value_changed.connect(self._on_value_change) self.default_key = self._key() - self.default_value = self.value_input.item_value() + self.global_value = self.value_input.item_value() self.override_key = None self.override_value = None @@ -1292,7 +1292,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): if self.count() == 0: self.add_row() - self.default_value = self.config_value() + self.global_value = self.config_value() self.override_value = None @property @@ -1335,7 +1335,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): if value is not None and key is not None: item_widget.default_key = key item_widget.key_input.setText(key) - item_widget.value_input.set_value(value, default_value=True) + item_widget.value_input.set_value(value, global_value=True) else: self._on_value_change() self.parent().updateGeometry() @@ -1407,7 +1407,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): self.key = input_data["key"] - self.default_value = self.item_value() + self.global_value = self.item_value() self.override_value = None def _on_value_change(self, item=None): @@ -1420,7 +1420,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): if self.is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.default_value + self._is_modified = self.item_value() != self.global_value self.value_changed.emit(self) @@ -1433,7 +1433,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): if override_value is None: self._is_overriden = False self._was_overriden = False - value = self.default_value + value = self.global_value else: self._is_overriden = True self._was_overriden = True From 4aca6fd3378615ae8adcf237a05dafce28194ca5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 16:08:14 +0200 Subject: [PATCH 519/947] override value is NOT_SET by default instead of None --- .../config_setting/widgets/base.py | 2 +- .../config_setting/widgets/inputs.py | 62 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 4fe3866096..94754bda0d 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -329,7 +329,7 @@ class ProjectWidget(QtWidgets.QWidget): def _on_project_change(self): project_name = self.project_list_widget.project_name() if project_name is None: - overrides = None + overrides = lib.NOT_SET self.is_overidable = False else: overrides = config.project_configurations_overrides(project_name) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 140d67c0ad..1697271b4d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -165,7 +165,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self.checkbox.setChecked(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET self.checkbox.stateChanged.connect(self._on_value_change) @@ -180,7 +180,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self._on_value_change() def reset_value(self): - if self.is_overidable and self.override_value is not None: + if self.is_overidable and self.override_value is not NOT_SET: self.set_value(self.override_value) else: self.set_value(self.global_value) @@ -192,7 +192,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False value = self.global_value @@ -293,7 +293,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): self.int_input.setValue(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET self.int_input.valueChanged.connect(self._on_value_change) @@ -313,7 +313,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False value = self.global_value @@ -417,7 +417,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self.float_input.setValue(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET self.float_input.valueChanged.connect(self._on_value_change) @@ -434,7 +434,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False value = self.global_value else: @@ -529,7 +529,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): self.text_input.setText(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET self.text_input.textChanged.connect(self._on_value_change) @@ -546,7 +546,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False value = self.global_value @@ -641,7 +641,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self.text_input.setPlainText(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET self.text_input.textChanged.connect(self._on_value_change) @@ -658,7 +658,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False value = self.global_value else: @@ -834,7 +834,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.text_input.set_value(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET self.text_input.textChanged.connect(self._on_value_change) @@ -854,7 +854,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False value = self.global_value else: @@ -970,7 +970,7 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): self.set_value(value) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET def set_value(self, value, *, global_value=False): for input_field in self.input_fields: @@ -1104,7 +1104,7 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self.setLayout(layout) self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -1133,7 +1133,7 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self._is_modified = False self._state = None self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False value = self.global_value else: @@ -1203,7 +1203,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): self.global_value = self.value_input.item_value() self.override_key = None - self.override_value = None + self.override_value = NOT_SET self.is_single = False @@ -1293,7 +1293,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): self.add_row() self.global_value = self.config_value() - self.override_value = None + self.override_value = NOT_SET @property def is_group(self): @@ -1408,7 +1408,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): self.key = input_data["key"] self.global_value = self.item_value() - self.override_value = None + self.override_value = NOT_SET def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -1430,7 +1430,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): self._state = None self._is_modified = False self.override_value = override_value - if override_value is None: + if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False value = self.global_value @@ -1523,10 +1523,10 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): self._state = None self._child_state = None for item in self.input_fields: - if override_value is None: - child_value = None + if override_value is NOT_SET: + child_value = NOT_SET else: - child_value = override_value.get(item.key) + child_value = override_value.get(item.key, NOT_SET) item.apply_overrides(child_value) @@ -1534,7 +1534,7 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): self.is_group and self.is_overidable and ( - override_value is not None + override_value is not NOT_SET or self.child_overriden ) ) @@ -1710,10 +1710,10 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): self._state = None self._child_state = None for item in self.input_fields: - if override_value is None: - child_value = None + if override_value is NOT_SET: + child_value = NOT_SET else: - child_value = override_value.get(item.key) + child_value = override_value.get(item.key, NOT_SET) item.apply_overrides(child_value) @@ -1721,7 +1721,7 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): self.is_group and self.is_overidable and ( - override_value is not None + override_value is not NOT_SET or self.child_overriden ) ) @@ -1919,10 +1919,10 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): def apply_overrides(self, override_value): self._is_overriden = False for item in self.input_fields: - if override_value is None: - child_value = None + if override_value is NOT_SET: + child_value = NOT_SET else: - child_value = override_value.get(item.key) + child_value = override_value.get(item.key, NOT_SET) item.apply_overrides(child_value) self._is_overriden = ( From 6342f02574ec0a0560e659161953d793bf9bd1cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 17:00:03 +0200 Subject: [PATCH 520/947] it is possible to right click on widgets, actions do nothing --- .../config_setting/widgets/inputs.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 1697271b4d..baf524d5da 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -94,6 +94,41 @@ class ConfigWidget: "Method `add_children_gui` is not implemented for `{}`." ).format(self.__class__.__name__)) + def _discard_changes(self): + print("_discard_changes") + + def _remove_overrides(self): + print("_remove_overrides") + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + menu = QtWidgets.QMenu() + + actions_mapping = {} + if self.child_modified: + action = QtWidgets.QAction("Discard changes") + actions_mapping[action] = self._discard_changes + menu.addAction(action) + + if self.child_overriden: + # TODO better label + action = QtWidgets.QAction("Remove override") + actions_mapping[action] = self._remove_overrides + menu.addAction(action) + + if not actions_mapping: + action = QtWidgets.QAction("< No action >") + actions_mapping[action] = None + menu.addAction(action) + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + to_run = actions_mapping[result] + if to_run: + to_run() + return + super(self.__class__, self).mouseReleaseEvent(event) + class InputWidget(ConfigWidget): def overrides(self): From 31d75aa5cc24c7fdcf86ff5c9f10941d2b3ecd46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 18:09:02 +0200 Subject: [PATCH 521/947] styles are automatically updated on _ignore_value_changes changed to False --- .../config_setting/widgets/base.py | 33 +++++++++++++++++-- .../config_setting/widgets/inputs.py | 20 +++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 94754bda0d..2018b4bde3 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -12,11 +12,12 @@ class StudioWidget(QtWidgets.QWidget): is_overriden = False is_group = False any_parent_is_group = False - ignore_value_changes = False def __init__(self, parent=None): super(StudioWidget, self).__init__(parent) + self._ignore_value_changes = False + self.input_fields = [] scroll_widget = QtWidgets.QScrollArea(self) @@ -55,6 +56,20 @@ class StudioWidget(QtWidgets.QWidget): self.reset() + @property + def ignore_value_changes(self): + return self._ignore_value_changes + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + self._ignore_value_changes = value + if value is False: + self.hierarchical_style_update() + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + def reset(self): if self.content_layout.count() != 0: for widget in self.input_fields: @@ -256,7 +271,7 @@ class ProjectWidget(QtWidgets.QWidget): super(ProjectWidget, self).__init__(parent) self.is_overidable = False - self.ignore_value_changes = False + self._ignore_value_changes = False self.project_name = None self.input_fields = [] @@ -310,6 +325,20 @@ class ProjectWidget(QtWidgets.QWidget): self.reset() + @property + def ignore_value_changes(self): + return self._ignore_value_changes + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + self._ignore_value_changes = value + if value is False: + self.hierarchical_style_update() + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + def reset(self): values = {"project": config.global_project_configurations()} schema = lib.gui_schema("projects_schema", "0_project_gui_schema") diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index baf524d5da..c5afe1a7e7 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -53,6 +53,9 @@ class ConfigWidget: raise NotImplementedError( "Method `reset_children_attributes` not implemented!" ) + @ignore_value_changes.setter + def ignore_value_changes(self, value): + self._parent.ignore_value_changes = value def item_value(self): raise NotImplementedError( @@ -136,6 +139,9 @@ class InputWidget(ConfigWidget): return NOT_SET, False return self.config_value(), self.is_group + def hierarchical_style_update(self): + self.update_style() + @property def child_modified(self): return self.is_modified @@ -1780,6 +1786,11 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): self.update_style() + def hierarchical_style_update(self): + self.update_style() + for input_field in self.input_fields: + input_field.hierarchical_style_update() + def update_style(self, is_overriden=None): child_modified = self.child_modified child_state = self.style_state(self.child_overriden, child_modified) @@ -1951,6 +1962,11 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): self.value_changed.emit(self) + def hierarchical_style_update(self): + self.update_style() + for input_field in self.input_fields: + input_field.hierarchical_style_update() + def apply_overrides(self, override_value): self._is_overriden = False for item in self.input_fields: @@ -2051,6 +2067,10 @@ class DictFormWidget(QtWidgets.QWidget, ConfigWidget): self.input_fields[key] = item return item + def hierarchical_style_update(self): + for input_field in self.input_fields.items(): + input_field.hierarchical_style_update() + def item_value(self): output = {} for input_field in self.input_fields.values(): From 6a6a50978460f5302b782b653d08c0cbfb7ac9cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:09:19 +0200 Subject: [PATCH 522/947] global_value split into global_value and start_value --- .../config_setting/widgets/inputs.py | 144 +++++++++++------- 1 file changed, 91 insertions(+), 53 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c5afe1a7e7..2bda38e207 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -194,6 +194,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(self.checkbox, 1) + value = NOT_SET if not self._as_widget: self.label_widget = label_widget self.key = input_data["key"] @@ -201,11 +202,16 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): keys.append(self.key) self.keys = keys + default_value = input_data.get("default", NOT_SET) value = self.value_from_values(values) if value is not NOT_SET: self.checkbox.setChecked(value) - self.global_value = self.item_value() + elif default_value is not NOT_SET: + self.checkbox.setChecked(default_value) + + self.global_value = value + self.start_value = self.item_value() self.override_value = NOT_SET self.checkbox.stateChanged.connect(self._on_value_change) @@ -236,7 +242,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False - value = self.global_value + value = self.start_value else: self._is_overriden = True self._was_overriden = True @@ -321,6 +327,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(label_widget, 0) layout.addWidget(self.int_input, 1) + value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -333,7 +340,8 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.int_input.setValue(value) - self.global_value = self.item_value() + self.global_value = value + self.start_value = self.item_value() self.override_value = NOT_SET self.int_input.valueChanged.connect(self._on_value_change) @@ -341,6 +349,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): def set_value(self, value, *, global_value=False): self.int_input.setValue(value) if global_value: + self.start_value = self.item_value() self.global_value = self.item_value() self._on_value_change() @@ -348,7 +357,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): self.set_value(0) def reset_value(self): - self.set_value(self.global_value) + self.set_value(self.start_value) def apply_overrides(self, override_value): self._is_modified = False @@ -445,6 +454,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(label_widget, 0) layout.addWidget(self.float_input, 1) + value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -457,7 +467,8 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.float_input.setValue(value) - self.global_value = self.item_value() + self.start_value = self.item_value() + self.global_value = value self.override_value = NOT_SET self.float_input.valueChanged.connect(self._on_value_change) @@ -465,6 +476,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): def set_value(self, value, *, global_value=False): self.float_input.setValue(value) if global_value: + self.start_value = self.item_value() self.global_value = self.item_value() self._on_value_change() @@ -477,7 +489,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is NOT_SET: self._is_overriden = False - value = self.global_value + value = self.start_value else: self._is_overriden = True value = override_value @@ -557,6 +569,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(label_widget, 0) layout.addWidget(self.text_input, 1) + value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -569,7 +582,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): if value is not NOT_SET: self.text_input.setText(value) - self.global_value = self.item_value() + self.global_value = value + self.start_value = self.item_value() self.override_value = NOT_SET self.text_input.textChanged.connect(self._on_value_change) @@ -577,11 +591,12 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): def set_value(self, value, *, global_value=False): self.text_input.setText(value) if global_value: + self.start_value = self.item_value() self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.global_value) + self.set_value(self.start_value) def apply_overrides(self, override_value): self._is_modified = False @@ -590,7 +605,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False - value = self.global_value + value = self.start_value else: self._is_overriden = True self._was_overriden = True @@ -664,24 +679,27 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): layout.setSpacing(5) self.text_input = QtWidgets.QPlainTextEdit() - if not label_widget: + if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) layout.addWidget(self.text_input, 1) - self.label_widget = label_widget + value = NOT_SET + if not self._as_widget: + self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys - value = self.value_from_values(values) - if value is not NOT_SET: - self.text_input.setPlainText(value) + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setPlainText(value) - self.global_value = self.item_value() + self.global_value = value + self.start_value = self.item_value() self.override_value = NOT_SET self.text_input.textChanged.connect(self._on_value_change) @@ -689,11 +707,12 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): def set_value(self, value, *, global_value=False): self.text_input.setPlainText(value) if global_value: + self.start_value = self.item_value() self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.global_value) + self.set_value(self.start_value) def apply_overrides(self, override_value): self._is_modified = False @@ -701,7 +720,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is NOT_SET: self._is_overriden = False - value = self.global_value + value = self.start_value else: self._is_overriden = True value = override_value @@ -857,24 +876,27 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): QtWidgets.QSizePolicy.MinimumExpanding ) - if not label_widget: + if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) layout.addWidget(self.text_input, 1) - self.label_widget = label_widget + value = NOT_SET + if not self._as_widget: + self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys - value = self.value_from_values(values) - if value is not NOT_SET: - self.text_input.set_value(value) + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.set_value(value) - self.global_value = self.item_value() + self.global_value = value + self.start_value = self.item_value() self.override_value = NOT_SET self.text_input.textChanged.connect(self._on_value_change) @@ -882,11 +904,12 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): def set_value(self, value, *, global_value=False): self.text_input.set_value(value) if global_value: + self.start_value = self.item_value() self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.global_value) + self.set_value(self.start_value) def clear_value(self): self.set_value("") @@ -897,7 +920,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): self.override_value = override_value if override_value is NOT_SET: self._is_overriden = False - value = self.global_value + value = self.start_value else: self._is_overriden = True value = override_value @@ -1010,7 +1033,8 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): if value is not NOT_SET: self.set_value(value) - self.global_value = self.item_value() + self.global_value = value + self.start_value = self.item_value() self.override_value = NOT_SET def set_value(self, value, *, global_value=False): @@ -1021,11 +1045,23 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): self.add_row(text=item_text) if global_value: - self.global_value = self.item_value() + self.global_value = value + self.start_value = self.item_value() self._on_value_change() + def apply_overrides(self, override_value): + self.override_value = override_value + if override_value is NOT_SET: + self._is_overriden = False + value = self.start_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + def reset_value(self): - self.set_value(self.global_value) + self.set_value(self.start_value) def clear_value(self): self.set_value([]) @@ -1144,8 +1180,17 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): layout.addWidget(self.value_widget) self.setLayout(layout) - self.global_value = self.item_value() - self.override_value = NOT_SET + @property + def start_value(self): + return self.value_widget.start_value + + @property + def global_value(self): + return self.value_widget.global_value + + @property + def override_value(self): + return self.value_widget.override_value def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -1159,29 +1204,20 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): self.value_changed.emit(self) def set_value(self, value, *, global_value=False): - self.value_widget.set_value(value) + self.value_widget.set_value(value, global_value=global_value) if global_value: - self.global_value = self.item_value() self._on_value_change() def reset_value(self): - self.set_value(self.global_value) + self.value_widget.reset_value() def clear_value(self): - self.set_value([]) + self.value_widget.clear_value() def apply_overrides(self, override_value): + self.value_widget.apply_overrides(override_value) self._is_modified = False self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - value = self.global_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) self.update_style() def update_style(self): @@ -1240,10 +1276,11 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): self.key_input.textChanged.connect(self._on_value_change) self.value_input.value_changed.connect(self._on_value_change) + # TODO This doesn't make sence! self.default_key = self._key() self.global_value = self.value_input.item_value() - self.override_key = None + self.override_key = NOT_SET self.override_value = NOT_SET self.is_single = False @@ -1333,7 +1370,8 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): if self.count() == 0: self.add_row() - self.global_value = self.config_value() + self.global_value = value + self.start_value = self.config_value() self.override_value = NOT_SET @property From 8361db1f79620e77ebb484c79bc3d4b4a770d822 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:09:38 +0200 Subject: [PATCH 523/947] removed unused methods --- .../config_setting/config_setting/widgets/inputs.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 2bda38e207..7abbd87863 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -42,17 +42,6 @@ class ConfigWidget: def ignore_value_changes(self): return self._parent.ignore_value_changes - def reset_attributes(self): - self._is_overriden = False - self._is_modified = False - self._was_overriden = False - - self.reset_children_attributes() - - def reset_children_attributes(self): - raise NotImplementedError( - "Method `reset_children_attributes` not implemented!" - ) @ignore_value_changes.setter def ignore_value_changes(self, value): self._parent.ignore_value_changes = value From e1209b2f13e29a8f5cdef2dc158cbaf847e1ca86 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:09:51 +0200 Subject: [PATCH 524/947] fix value_from_values --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 7abbd87863..04a13ca487 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -55,7 +55,7 @@ class ConfigWidget: return {self.key: self.item_value()} def value_from_values(self, values, keys=None): - if not values: + if not values or values is AS_WIDGET: return NOT_SET if keys is None: From 6c77e43d6b334d5a3c3937b41b221ca1437e6a42 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:10:30 +0200 Subject: [PATCH 525/947] made preparation for abstract methods --- .../config_setting/widgets/inputs.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 04a13ca487..3b07bb1c99 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -86,11 +86,23 @@ class ConfigWidget: "Method `add_children_gui` is not implemented for `{}`." ).format(self.__class__.__name__)) - def _discard_changes(self): - print("_discard_changes") + def discard_changes(self, is_source=False): + print("discard_changes") + # raise NotImplementedError( + # "Method `discard_changes` not implemented!" + # ) - def _remove_overrides(self): - print("_remove_overrides") + def remove_overrides(self, is_source=False): + print("remove_overrides") + # raise NotImplementedError( + # "Method `remove_overrides` not implemented!" + # ) + + def hierarchical_style_update(self): + print("hierarchical_style_update") + # raise NotImplementedError( + # "Method `hierarchical_style_update` not implemented!" + # ) def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton: From 403c2f4888a9e7c6401032e22c62847ec8e9e42d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:10:46 +0200 Subject: [PATCH 526/947] preparation for discard changes --- .../config_setting/widgets/inputs.py | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3b07bb1c99..7fce3606f5 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -111,13 +111,13 @@ class ConfigWidget: actions_mapping = {} if self.child_modified: action = QtWidgets.QAction("Discard changes") - actions_mapping[action] = self._discard_changes + actions_mapping[action] = self.discard_changes menu.addAction(action) if self.child_overriden: # TODO better label action = QtWidgets.QAction("Remove override") - actions_mapping[action] = self._remove_overrides + actions_mapping[action] = self.remove_overrides menu.addAction(action) if not actions_mapping: @@ -129,7 +129,7 @@ class ConfigWidget: if result: to_run = actions_mapping[result] if to_run: - to_run() + to_run(True) return super(self.__class__, self).mouseReleaseEvent(event) @@ -143,6 +143,24 @@ class InputWidget(ConfigWidget): def hierarchical_style_update(self): self.update_style() + def discard_changes(self, is_source=False): + if ( + self.is_overidable + and self.override_value is not NOT_SET + and self._was_overriden is True + ): + self.set_value(self.override_value) + else: + self.set_value(self.start_value) + + if not self.is_overidable: + self._is_modified = self.global_value != self.item_value() + self._is_overriden = False + return + + self._is_modified = False + self._is_overriden = self._was_overriden + @property def child_modified(self): return self.is_modified From 82949978488a627f4031c36d242fb4b3c9c7f2c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:11:39 +0200 Subject: [PATCH 527/947] implemented discard changes for most of input widgets --- .../config_setting/widgets/inputs.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 7fce3606f5..b593248470 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1615,6 +1615,19 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def discard_changes(self, is_source=False): + if is_source: + self.ignore_value_changes = True + + for item in self.input_fields: + item.discard_changes() + + if is_source: + self.ignore_value_changes = False + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -1636,6 +1649,7 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): or self.child_overriden ) ) + self._was_overriden = bool(self._is_overriden) self.update_style() def _on_value_change(self, item=None): @@ -1802,6 +1816,19 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def discard_changes(self, is_source=False): + if is_source: + self.ignore_value_changes = True + + for item in self.input_fields: + item.discard_changes() + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + + if is_source: + self.ignore_value_changes = False + def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False From 43da05a6183b6270b252c08c613e84fb164ed315 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:12:09 +0200 Subject: [PATCH 528/947] added forgotten hierarchical style update --- .../config_setting/config_setting/widgets/inputs.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b593248470..5e4fe0bbf3 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -245,14 +245,8 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): self._on_value_change() - def reset_value(self): - if self.is_overidable and self.override_value is not NOT_SET: - self.set_value(self.override_value) - else: - self.set_value(self.global_value) - def clear_value(self): - self.reset_value() + self.set_value(False) def apply_overrides(self, override_value): self._is_modified = False @@ -1670,6 +1664,11 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): self.update_style() + def hierarchical_style_update(self): + self.update_style() + for input_field in self.input_fields: + input_field.hierarchical_style_update() + def update_style(self, is_overriden=None): child_modified = self.child_modified child_state = self.style_state(self.child_overriden, child_modified) From 82570533d0512f7c263fd02892feeb850bff2fca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:14:46 +0200 Subject: [PATCH 529/947] hierarchical_style_update and discard_changes are abstract methods now --- .../config_setting/widgets/inputs.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 5e4fe0bbf3..33facc1a18 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -87,10 +87,9 @@ class ConfigWidget: ).format(self.__class__.__name__)) def discard_changes(self, is_source=False): - print("discard_changes") - # raise NotImplementedError( - # "Method `discard_changes` not implemented!" - # ) + raise NotImplementedError( + "Method `discard_changes` not implemented!" + ) def remove_overrides(self, is_source=False): print("remove_overrides") @@ -99,10 +98,9 @@ class ConfigWidget: # ) def hierarchical_style_update(self): - print("hierarchical_style_update") - # raise NotImplementedError( - # "Method `hierarchical_style_update` not implemented!" - # ) + raise NotImplementedError( + "Method `hierarchical_style_update` not implemented!" + ) def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton: From e343cd65cfb6c3682d41ff110fe6ce795fbbc1d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:36:27 +0200 Subject: [PATCH 530/947] wrapped discard_changes remove_overrides to proxy methods setting ignore value changes --- .../config_setting/widgets/inputs.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 33facc1a18..379b27cdb6 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -86,12 +86,22 @@ class ConfigWidget: "Method `add_children_gui` is not implemented for `{}`." ).format(self.__class__.__name__)) - def discard_changes(self, is_source=False): + def _discard_changes(self): + self.ignore_value_changes = True + self.discard_changes() + self.ignore_value_changes = False + + def discard_changes(self): raise NotImplementedError( - "Method `discard_changes` not implemented!" + "{} Method `discard_changes` not implemented!".format(repr(self)) ) - def remove_overrides(self, is_source=False): + def _remove_overrides(self): + self.ignore_value_changes = True + self.remove_overrides() + self.ignore_value_changes = False + + def remove_overrides(self): print("remove_overrides") # raise NotImplementedError( # "Method `remove_overrides` not implemented!" @@ -109,13 +119,16 @@ class ConfigWidget: actions_mapping = {} if self.child_modified: action = QtWidgets.QAction("Discard changes") - actions_mapping[action] = self.discard_changes + actions_mapping[action] = self._discard_changes menu.addAction(action) - if self.child_overriden: + if ( + not self.any_parent_overriden() + and (self.is_overriden or self.child_overriden) + ): # TODO better label action = QtWidgets.QAction("Remove override") - actions_mapping[action] = self.remove_overrides + actions_mapping[action] = self._remove_overrides menu.addAction(action) if not actions_mapping: @@ -127,7 +140,7 @@ class ConfigWidget: if result: to_run = actions_mapping[result] if to_run: - to_run(True) + to_run() return super(self.__class__, self).mouseReleaseEvent(event) From f80cd9492e2338cb80d3d9940cdfb70af8818b75 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:37:09 +0200 Subject: [PATCH 531/947] implemented missing discard changes --- .../config_setting/widgets/inputs.py | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 379b27cdb6..feae2d2943 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -154,7 +154,7 @@ class InputWidget(ConfigWidget): def hierarchical_style_update(self): self.update_style() - def discard_changes(self, is_source=False): + def discard_changes(self): if ( self.is_overidable and self.override_value is not NOT_SET @@ -1620,16 +1620,10 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) - def discard_changes(self, is_source=False): - if is_source: - self.ignore_value_changes = True - + def discard_changes(self): for item in self.input_fields: item.discard_changes() - if is_source: - self.ignore_value_changes = False - self._is_modified = self.child_modified self._is_overriden = self._was_overriden @@ -1826,19 +1820,13 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) - def discard_changes(self, is_source=False): - if is_source: - self.ignore_value_changes = True - + def discard_changes(self): for item in self.input_fields: item.discard_changes() self._is_modified = self.child_modified self._is_overriden = self._was_overriden - if is_source: - self.ignore_value_changes = False - def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -2061,6 +2049,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): for input_field in self.input_fields: input_field.hierarchical_style_update() + def discard_changes(self): + for item in self.input_fields: + item.discard_changes() + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + def apply_overrides(self, override_value): self._is_overriden = False for item in self.input_fields: From dba9c98060de3a650e54412e4b82ffcf3d68de89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:37:37 +0200 Subject: [PATCH 532/947] added any_parent_overriden for remove overrides action --- .../config_setting/widgets/base.py | 42 ++++++++++++++++--- .../config_setting/widgets/inputs.py | 5 +++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 2018b4bde3..c0246dd8a2 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -9,9 +9,9 @@ from avalon import io class StudioWidget(QtWidgets.QWidget): is_overidable = False - is_overriden = False - is_group = False - any_parent_is_group = False + _is_overriden = False + _is_group = False + _any_parent_is_group = False def __init__(self, parent=None): super(StudioWidget, self).__init__(parent) @@ -56,6 +56,21 @@ class StudioWidget(QtWidgets.QWidget): self.reset() + def any_parent_overriden(self): + return False + + @property + def is_overriden(self): + return self._is_overriden + + @property + def is_group(self): + return self._is_group + + @property + def any_parent_is_group(self): + return self._any_parent_is_group + @property def ignore_value_changes(self): return self._ignore_value_changes @@ -263,9 +278,9 @@ class ProjectListWidget(QtWidgets.QWidget): class ProjectWidget(QtWidgets.QWidget): - is_overriden = False - is_group = False - any_parent_is_group = False + _is_overriden = False + _is_group = False + _any_parent_is_group = False def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) @@ -325,6 +340,21 @@ class ProjectWidget(QtWidgets.QWidget): self.reset() + def any_parent_overriden(self): + return False + + @property + def is_overriden(self): + return self._is_overriden + + @property + def is_group(self): + return self._is_group + + @property + def any_parent_is_group(self): + return self._any_parent_is_group + @property def ignore_value_changes(self): return self._ignore_value_changes diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index feae2d2943..f946b4cac8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -38,6 +38,11 @@ class ConfigWidget: def is_overidable(self): return self._parent.is_overidable + def any_parent_overriden(self): + if self._parent._is_overriden: + return True + return self._parent.any_parent_overriden() + @property def ignore_value_changes(self): return self._parent.ignore_value_changes From 9923427df8e5d285bc89df945830fe73d182cfb7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:37:56 +0200 Subject: [PATCH 533/947] added remove overrides for inputs --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f946b4cac8..ff0a871e17 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -159,6 +159,12 @@ class InputWidget(ConfigWidget): def hierarchical_style_update(self): self.update_style() + def remove_overrides(self): + self.set_value(self.start_value) + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + def discard_changes(self): if ( self.is_overidable From 71e61a6c68d86ac2b9c253fd46a8d7665d028372 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:40:16 +0200 Subject: [PATCH 534/947] remove_overrides is abstract --- .../config_setting/widgets/inputs.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ff0a871e17..d67d32990a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -87,9 +87,11 @@ class ConfigWidget: return "-".join(items) or self.default_state def add_children_gui(self, child_configuration, values): - raise NotImplementedError(( - "Method `add_children_gui` is not implemented for `{}`." - ).format(self.__class__.__name__)) + raise NotImplementedError( + "{} Method `add_children_gui` is not implemented!.".format( + repr(self) + ) + ) def _discard_changes(self): self.ignore_value_changes = True @@ -98,7 +100,9 @@ class ConfigWidget: def discard_changes(self): raise NotImplementedError( - "{} Method `discard_changes` not implemented!".format(repr(self)) + "{} Method `discard_changes` not implemented!".format( + repr(self) + ) ) def _remove_overrides(self): @@ -107,10 +111,11 @@ class ConfigWidget: self.ignore_value_changes = False def remove_overrides(self): - print("remove_overrides") - # raise NotImplementedError( - # "Method `remove_overrides` not implemented!" - # ) + raise NotImplementedError( + "{} Method `remove_overrides` not implemented!".format( + repr(self) + ) + ) def hierarchical_style_update(self): raise NotImplementedError( From 2a65a2ccb97a42b4bcaed9925a259fba1a011d46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 25 Aug 2020 19:42:31 +0200 Subject: [PATCH 535/947] remove overrides should work now --- .../config_setting/widgets/inputs.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d67d32990a..3e344cd703 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1636,6 +1636,13 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + for item in self.input_fields: + item.remove_overrides() + def discard_changes(self): for item in self.input_fields: item.discard_changes() @@ -1836,6 +1843,13 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + for item in self.input_fields: + item.remove_overrides() + def discard_changes(self): for item in self.input_fields: item.discard_changes() @@ -2065,6 +2079,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): for input_field in self.input_fields: input_field.hierarchical_style_update() + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + for item in self.input_fields: + item.remove_overrides() + def discard_changes(self): for item in self.input_fields: item.discard_changes() From e1a203125833d96844be994ee993724e8b532c50 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 24 Aug 2020 20:27:03 +0100 Subject: [PATCH 536/947] Handle original file missing and destination file existing. --- pype/plugins/global/publish/integrate_new.py | 36 ++++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index a3c2ffe52b..24f5b7bddc 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -6,6 +6,8 @@ import copy import clique import errno import six +import re +import shutil from pymongo import DeleteOne, InsertOne import pyblish.api @@ -952,21 +954,35 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): """ if integrated_file_sizes: for file_url, _file_size in integrated_file_sizes.items(): + if not os.path.exists(file_url): + self.log.debug( + "File {} was not found.".format(file_url) + ) + continue + try: if mode == 'remove': - self.log.debug("Removing file ...{}".format(file_url)) + self.log.debug("Removing file {}".format(file_url)) os.remove(file_url) if mode == 'finalize': - self.log.debug("Renaming file ...{}".format(file_url)) - import re - os.rename(file_url, - re.sub('\.{}$'.format(self.TMP_FILE_EXT), - '', - file_url) - ) + new_name = re.sub( + r'\.{}$'.format(self.TMP_FILE_EXT), + '', + file_url + ) - except FileNotFoundError: - pass # file not there, nothing to delete + if os.path.exists(new_name): + self.log.debug( + "Overwriting file {} to {}".format( + file_url, new_name + ) + ) + shutil.copy(file_url, new_name) + else: + self.log.debug( + "Renaming file {} to {}".format(file_url, new_name) + ) + os.rename(file_url, new_name) except OSError: self.log.error("Cannot {} file {}".format(mode, file_url), exc_info=True) From 432d715d2fe3fd8254d0e02bb5d3f910c4a7ecfb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 24 Aug 2020 20:28:58 +0100 Subject: [PATCH 537/947] Houd --- pype/plugins/global/publish/integrate_new.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 24f5b7bddc..f92968e554 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -980,7 +980,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): shutil.copy(file_url, new_name) else: self.log.debug( - "Renaming file {} to {}".format(file_url, new_name) + "Renaming file {} to {}".format( + file_url, new_name + ) ) os.rename(file_url, new_name) except OSError: From cdf32eb15051b75b17ed2187daefabc0a5fff43a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 26 Aug 2020 10:16:20 +0100 Subject: [PATCH 538/947] Containerize audio loading. --- pype/plugins/maya/load/load_audio.py | 49 +++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/pype/plugins/maya/load/load_audio.py b/pype/plugins/maya/load/load_audio.py index e1860d0ca6..ca38082ed0 100644 --- a/pype/plugins/maya/load/load_audio.py +++ b/pype/plugins/maya/load/load_audio.py @@ -1,6 +1,9 @@ from maya import cmds, mel +import pymel.core as pc from avalon import api +from avalon.maya.pipeline import containerise +from avalon.maya import lib class AudioLoader(api.Loader): @@ -24,4 +27,48 @@ class AudioLoader(api.Loader): displaySound=True ) - return [sound_node] + asset = context["asset"]["name"] + namespace = namespace or lib.unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + return containerise( + name=name, + namespace=namespace, + nodes=[sound_node], + context=context, + loader=self.__class__.__name__ + ) + + def update(self, container, representation): + audio_node = None + for node in pc.PyNode(container["objectName"]).members(): + if node.nodeType() == "audio": + audio_node = node + + assert audio_node is not None, "Audio node not found." + + path = api.get_representation_path(representation) + audio_node.filename.set(path) + cmds.setAttr( + container["objectName"] + ".representation", + str(representation["_id"]), + type="string" + ) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass From a3d82fc92c5ac27f348077685d36b7acb3517cc3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 26 Aug 2020 11:03:00 +0100 Subject: [PATCH 539/947] Enable previews for Ftrack review. --- pype/plugins/nukestudio/publish/collect_reviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py index aa8c60767c..c158dee876 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -99,7 +99,7 @@ class CollectReviews(api.InstancePlugin): "step": 1, "fps": rev_inst.data.get("fps"), "name": "preview", - "tags": ["preview"], + "tags": ["preview", "ftrackreview"], "ext": ext } From 76d65ca6d3dcb8772800a3013005dec25c93fe00 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 26 Aug 2020 11:06:49 +0100 Subject: [PATCH 540/947] Thumbnail parent Assetversion was not found when its not linked to a task. assetversion["task"] returns None instead of raising exception. --- .../ftrack/actions/action_thumbnail_to_parent.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pype/modules/ftrack/actions/action_thumbnail_to_parent.py b/pype/modules/ftrack/actions/action_thumbnail_to_parent.py index 8710fa9dcf..fb473f9aa5 100644 --- a/pype/modules/ftrack/actions/action_thumbnail_to_parent.py +++ b/pype/modules/ftrack/actions/action_thumbnail_to_parent.py @@ -41,9 +41,9 @@ class ThumbToParent(BaseAction): parent = None thumbid = None if entity.entity_type.lower() == 'assetversion': - try: - parent = entity['task'] - except Exception: + parent = entity['task'] + + if parent is None: par_ent = entity['link'][-2] parent = session.get(par_ent['type'], par_ent['id']) else: @@ -51,7 +51,7 @@ class ThumbToParent(BaseAction): parent = entity['parent'] except Exception as e: msg = ( - "Durin Action 'Thumb to Parent'" + "During Action 'Thumb to Parent'" " went something wrong" ) self.log.error(msg) @@ -62,7 +62,10 @@ class ThumbToParent(BaseAction): parent['thumbnail_id'] = thumbid status = 'done' else: - status = 'failed' + raise Exception( + "Parent or thumbnail id not found. Parent: {}. " + "Thumbnail id: {}".format(parent, thumbid) + ) # inform the user that the job is done job['status'] = status or 'done' From 76e0b5a7ae1037f24816fe1094e88b52997570ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 12:44:20 +0200 Subject: [PATCH 541/947] allow show icon in bar instead of python icon --- pype/tools/tray/pype_tray.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 9537b62581..a4cf4eabfe 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -537,6 +537,14 @@ class PypeTrayApplication(QtWidgets.QApplication): super(self.__class__, self).__init__(sys.argv) # Allows to close widgets without exiting app self.setQuitOnLastWindowClosed(False) + + # Allow show icon istead of python icon in task bar (Windows) + if os.name == "nt": + import ctypes + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + u"pype_tray" + ) + # Sets up splash splash_widget = self.set_splash() From bda8cb88017a7c61b225d3bcc4187323d3c27e7f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 12:44:32 +0200 Subject: [PATCH 542/947] login thread is not qthread based --- pype/modules/ftrack/tray/login_tools.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pype/modules/ftrack/tray/login_tools.py b/pype/modules/ftrack/tray/login_tools.py index 02982294f2..e7d22fbc19 100644 --- a/pype/modules/ftrack/tray/login_tools.py +++ b/pype/modules/ftrack/tray/login_tools.py @@ -2,7 +2,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer from urllib import parse import webbrowser import functools -from Qt import QtCore +import threading from pype.api import resources @@ -55,20 +55,17 @@ class LoginServerHandler(BaseHTTPRequestHandler): ) -class LoginServerThread(QtCore.QThread): +class LoginServerThread(threading.Thread): '''Login server thread.''' - # Login signal. - loginSignal = QtCore.Signal(object, object, object) - - def start(self, url): - '''Start thread.''' + def __init__(self, url, callback): self.url = url - super(LoginServerThread, self).start() + self.callback = callback + super(LoginServerThread, self).__init__() def _handle_login(self, api_user, api_key): '''Login to server with *api_user* and *api_key*.''' - self.loginSignal.emit(self.url, api_user, api_key) + self.callback(api_user, api_key) def run(self): '''Listen for events.''' From e0e4b4eb9f1050ed6778af3b5860447c7c78b738 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 12:44:50 +0200 Subject: [PATCH 543/947] login dialog was rewriten from base --- pype/modules/ftrack/tray/login_dialog.py | 446 ++++++++++++----------- 1 file changed, 224 insertions(+), 222 deletions(-) diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index e0614513a3..9ffd21fd30 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -7,309 +7,311 @@ from pype.api import resources from Qt import QtCore, QtGui, QtWidgets -class Login_Dialog_ui(QtWidgets.QWidget): - +class CredentialsDialog(QtWidgets.QDialog): SIZE_W = 300 SIZE_H = 230 - loginSignal = QtCore.Signal(object, object, object) - _login_server_thread = None - inputs = [] - buttons = [] - labels = [] + login_changed = QtCore.Signal() + logout_signal = QtCore.Signal() - def __init__(self, parent=None, is_event=False): + def __init__(self, parent=None): + super(CredentialsDialog, self).__init__(parent) - super(Login_Dialog_ui, self).__init__() + self.setWindowTitle("Pype - Ftrack Login") - self.parent = parent - self.is_event = is_event + self._login_server_thread = None + self._is_logged = False + self._in_advance_mode = False - if hasattr(parent, 'icon'): - self.setWindowIcon(self.parent.icon) - elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): - self.setWindowIcon(self.parent.parent.icon) - else: - icon = QtGui.QIcon(resources.pype_icon_filepath()) - self.setWindowIcon(icon) + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) self.setWindowFlags( QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint ) - self.loginSignal.connect(self.loginWithCredentials) - self._translate = QtCore.QCoreApplication.translate - - self.font = QtGui.QFont() - self.font.setFamily("DejaVu Sans Condensed") - self.font.setPointSize(9) - self.font.setBold(True) - self.font.setWeight(50) - self.font.setKerning(True) - - self.resize(self.SIZE_W, self.SIZE_H) self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) - self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) + self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100)) self.setStyleSheet(style.load_stylesheet()) - self.setLayout(self._main()) - self.setWindowTitle('Pype - Ftrack Login') + self.ui_init() - def _main(self): - self.main = QtWidgets.QVBoxLayout() - self.main.setObjectName("main") - - self.form = QtWidgets.QFormLayout() - self.form.setContentsMargins(10, 15, 10, 5) - self.form.setObjectName("form") - - self.ftsite_label = QtWidgets.QLabel("FTrack URL:") - self.ftsite_label.setFont(self.font) - self.ftsite_label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - self.ftsite_label.setTextFormat(QtCore.Qt.RichText) - self.ftsite_label.setObjectName("user_label") + def ui_init(self): + self.ftsite_label = QtWidgets.QLabel("Ftrack URL:") + self.user_label = QtWidgets.QLabel("Username:") + self.api_label = QtWidgets.QLabel("API Key:") self.ftsite_input = QtWidgets.QLineEdit() - self.ftsite_input.setEnabled(True) - self.ftsite_input.setFrame(True) - self.ftsite_input.setEnabled(False) self.ftsite_input.setReadOnly(True) - self.ftsite_input.setObjectName("ftsite_input") - - self.user_label = QtWidgets.QLabel("Username:") - self.user_label.setFont(self.font) - self.user_label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - self.user_label.setTextFormat(QtCore.Qt.RichText) - self.user_label.setObjectName("user_label") + self.ftsite_input.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) self.user_input = QtWidgets.QLineEdit() - self.user_input.setEnabled(True) - self.user_input.setFrame(True) - self.user_input.setObjectName("user_input") - self.user_input.setPlaceholderText( - self._translate("main", "user.name") - ) + self.user_input.setPlaceholderText("user.name") self.user_input.textChanged.connect(self._user_changed) - self.api_label = QtWidgets.QLabel("API Key:") - self.api_label.setFont(self.font) - self.api_label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - self.api_label.setTextFormat(QtCore.Qt.RichText) - self.api_label.setObjectName("api_label") - self.api_input = QtWidgets.QLineEdit() - self.api_input.setEnabled(True) - self.api_input.setFrame(True) - self.api_input.setObjectName("api_input") - self.api_input.setPlaceholderText(self._translate( - "main", "e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - )) + self.api_input.setPlaceholderText( + "e.g. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + ) self.api_input.textChanged.connect(self._api_changed) + input_layout = QtWidgets.QFormLayout() + input_layout.setContentsMargins(10, 15, 10, 5) + + input_layout.addRow(self.ftsite_label, self.ftsite_input) + input_layout.addRow(self.user_label, self.user_input) + input_layout.addRow(self.api_label, self.api_input) + + self.btn_advanced = QtWidgets.QPushButton("Advanced") + self.btn_advanced.clicked.connect(self._on_advanced_clicked) + + self.btn_simple = QtWidgets.QPushButton("Simple") + self.btn_simple.clicked.connect(self._on_simple_clicked) + + self.btn_login = QtWidgets.QPushButton("Login") + self.btn_login.setToolTip( + "Set Username and API Key with entered values" + ) + self.btn_login.clicked.connect(self._on_login_clicked) + + self.btn_ftrack_login = QtWidgets.QPushButton("Ftrack login") + self.btn_ftrack_login.setToolTip("Open browser for Login to Ftrack") + self.btn_ftrack_login.clicked.connect(self._on_ftrack_login_clicked) + + self.btn_logout = QtWidgets.QPushButton("Logout") + self.btn_logout.clicked.connect(self._on_logout_clicked) + + self.btn_close = QtWidgets.QPushButton("Close") + self.btn_close.setToolTip("Close this window") + self.btn_close.clicked.connect(self._close_widget) + + btns_layout = QtWidgets.QHBoxLayout() + btns_layout.addWidget(self.btn_advanced) + btns_layout.addWidget(self.btn_simple) + btns_layout.addStretch(1) + btns_layout.addWidget(self.btn_ftrack_login) + btns_layout.addWidget(self.btn_login) + btns_layout.addWidget(self.btn_logout) + btns_layout.addWidget(self.btn_close) + + self.note_label = QtWidgets.QLabel(( + "NOTE: Click on \"{}\" button to log with your default browser" + " or click on \"{}\" button to enter API key manually." + ).format(self.btn_ftrack_login.text(), self.btn_advanced.text())) + + self.note_label.setWordWrap(True) + self.note_label.hide() + self.error_label = QtWidgets.QLabel("") - self.error_label.setFont(self.font) - self.error_label.setTextFormat(QtCore.Qt.RichText) - self.error_label.setObjectName("error_label") self.error_label.setWordWrap(True) self.error_label.hide() - self.form.addRow(self.ftsite_label, self.ftsite_input) - self.form.addRow(self.user_label, self.user_input) - self.form.addRow(self.api_label, self.api_input) - self.form.addRow(self.error_label) + label_layout = QtWidgets.QVBoxLayout() + label_layout.setContentsMargins(10, 5, 10, 5) + label_layout.addWidget(self.note_label) + label_layout.addWidget(self.error_label) - self.btnGroup = QtWidgets.QHBoxLayout() - self.btnGroup.addStretch(1) - self.btnGroup.setObjectName("btnGroup") + main = QtWidgets.QVBoxLayout(self) + main.addLayout(input_layout) + main.addLayout(label_layout) + main.addStretch(1) + main.addLayout(btns_layout) - self.btnEnter = QtWidgets.QPushButton("Login") - self.btnEnter.setToolTip( - 'Set Username and API Key with entered values' - ) - self.btnEnter.clicked.connect(self.enter_credentials) + self.fill_ftrack_url() - self.btnClose = QtWidgets.QPushButton("Close") - self.btnClose.setToolTip('Close this window') - self.btnClose.clicked.connect(self._close_widget) + self.set_is_logged(self._is_logged) - self.btnFtrack = QtWidgets.QPushButton("Ftrack") - self.btnFtrack.setToolTip('Open browser for Login to Ftrack') - self.btnFtrack.clicked.connect(self.open_ftrack) + self.setLayout(main) - self.btnGroup.addWidget(self.btnFtrack) - self.btnGroup.addWidget(self.btnEnter) - self.btnGroup.addWidget(self.btnClose) + def fill_ftrack_url(self): + url = os.getenv("FTRACK_SERVER") + checked_url = self.check_url(url) - self.main.addLayout(self.form) - self.main.addLayout(self.btnGroup) + if checked_url is None: + checked_url = "" + self.btn_login.setEnabled(False) + self.btn_ftrack_login.setEnabled(False) - self.inputs.append(self.api_input) - self.inputs.append(self.user_input) - self.inputs.append(self.ftsite_input) + self.api_input.setEnabled(False) + self.user_input.setEnabled(False) + self.ftsite_input.setEnabled(False) - self.enter_site() - return self.main + self.ftsite_input.setText(checked_url) - def enter_site(self): - try: - url = os.getenv('FTRACK_SERVER') - newurl = self.checkUrl(url) + def set_advanced_mode(self, is_advanced): + self._in_advance_mode = is_advanced - if newurl is None: - self.btnEnter.setEnabled(False) - self.btnFtrack.setEnabled(False) - for input in self.inputs: - input.setEnabled(False) - newurl = url + self.error_label.setVisible(False) - self.ftsite_input.setText(newurl) + is_logged = self._is_logged - except Exception: - self.setError("FTRACK_SERVER is not set in templates") - self.btnEnter.setEnabled(False) - self.btnFtrack.setEnabled(False) - for input in self.inputs: - input.setEnabled(False) + self.note_label.setVisible(not is_logged and not is_advanced) + self.btn_ftrack_login.setVisible(not is_logged and not is_advanced) + self.btn_advanced.setVisible(not is_logged and not is_advanced) - def setError(self, msg): + self.btn_login.setVisible(not is_logged and is_advanced) + self.btn_simple.setVisible(not is_logged and is_advanced) + + self.user_label.setVisible(is_logged or is_advanced) + self.user_input.setVisible(is_logged or is_advanced) + self.api_label.setVisible(is_logged or is_advanced) + self.api_input.setVisible(is_logged or is_advanced) + if is_advanced: + self.user_input.setFocus() + else: + self.btn_ftrack_login.setFocus() + + def set_is_logged(self, is_logged): + self._is_logged = is_logged + + self.user_input.setReadOnly(is_logged) + self.api_input.setReadOnly(is_logged) + self.user_input.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) + self.api_input.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) + + self.btn_logout.setVisible(is_logged) + + self.set_advanced_mode(self._in_advance_mode) + + def set_error(self, msg): self.error_label.setText(msg) self.error_label.show() + def _on_logout_clicked(self): + self.user_input.setText("") + self.api_input.setText("") + self.set_is_logged(False) + self.logout_signal.emit() + + def _on_simple_clicked(self): + self.set_advanced_mode(False) + + def _on_advanced_clicked(self): + self.set_advanced_mode(True) + def _user_changed(self): - self.user_input.setStyleSheet("") + self._not_invalid_input(self.user_input) def _api_changed(self): - self.api_input.setStyleSheet("") + self._not_invalid_input(self.api_input) - def _invalid_input(self, entity): - entity.setStyleSheet("border: 1px solid red;") + def _not_invalid_input(self, input_widget): + input_widget.setStyleSheet("") - def enter_credentials(self): + def _invalid_input(self, input_widget): + input_widget.setStyleSheet("border: 1px solid red;") + + def _on_login_clicked(self): username = self.user_input.text().strip() - apiKey = self.api_input.text().strip() - msg = "You didn't enter " + api_key = self.api_input.text().strip() missing = [] if username == "": missing.append("Username") self._invalid_input(self.user_input) - if apiKey == "": + if api_key == "": missing.append("API Key") self._invalid_input(self.api_input) if len(missing) > 0: - self.setError("{0} {1}".format(msg, " and ".join(missing))) + self.set_error("You didn't enter {}".format(" and ".join(missing))) return - verification = credentials.check_credentials(username, apiKey) - - if verification: - credentials.save_credentials(username, apiKey, self.is_event) - credentials.set_env(username, apiKey) - if self.parent is not None: - self.parent.loginChange() - self._close_widget() - else: + if not self.login_with_credentials(username, api_key): self._invalid_input(self.user_input) self._invalid_input(self.api_input) - self.setError( + self.set_error( "We're unable to sign in to Ftrack with these credentials" ) - def open_ftrack(self): - url = self.ftsite_input.text() - self.loginWithCredentials(url, None, None) - - def checkUrl(self, url): - url = url.strip('/ ') - + def _on_ftrack_login_clicked(self): + url = self.check_url(self.ftsite_input.text()) if not url: - self.setError("There is no URL set in Templates") - return - - if 'http' not in url: - if url.endswith('ftrackapp.com'): - url = 'https://' + url - else: - url = 'https://{0}.ftrackapp.com'.format(url) - try: - result = requests.get( - url, - # Old python API will not work with redirect. - allow_redirects=False - ) - except requests.exceptions.RequestException: - self.setError( - 'The server URL set in Templates could not be reached.' - ) - return - - if ( - result.status_code != 200 or 'FTRACK_VERSION' not in result.headers - ): - self.setError( - 'The server URL set in Templates is not a valid ftrack server.' - ) - return - return url - - def loginWithCredentials(self, url, username, apiKey): - url = url.strip('/ ') - - if not url: - self.setError( - 'You need to specify a valid server URL, ' - 'for example https://server-name.ftrackapp.com' - ) - return - - if 'http' not in url: - if url.endswith('ftrackapp.com'): - url = 'https://' + url - else: - url = 'https://{0}.ftrackapp.com'.format(url) - try: - result = requests.get( - url, - # Old python API will not work with redirect. - allow_redirects=False - ) - except requests.exceptions.RequestException: - self.setError( - 'The server URL you provided could not be reached.' - ) - return - - if ( - result.status_code != 200 or 'FTRACK_VERSION' not in result.headers - ): - self.setError( - 'The server URL you provided is not a valid ftrack server.' - ) return # If there is an existing server thread running we need to stop it. if self._login_server_thread: - self._login_server_thread.quit() + self._login_server_thread.stop() + self._login_server_thread.join() self._login_server_thread = None # If credentials are not properly set, try to get them using a http # server. - if not username or not apiKey: - self._login_server_thread = login_tools.LoginServerThread() - self._login_server_thread.loginSignal.connect(self.loginSignal) - self._login_server_thread.start(url) + self._login_server_thread = login_tools.LoginServerThread( + url, self._result_of_ftrack_thread + ) + self._login_server_thread.start() + + def _result_of_ftrack_thread(self, username, api_key): + if not self.login_with_credentials(username, api_key): + self._invalid_input(self.api_input) + self.set_error(( + "Somthing happened with Ftrack login." + " Try enter Username and API key manually." + )) + else: + self.set_is_logged(True) + + def login_with_credentials(self, username, api_key): + verification = credentials.check_credentials(username, api_key) + if verification: + credentials.save_credentials(username, api_key, False) + credentials.set_env(username, api_key) + self.set_credentials(username, api_key) + self.login_changed.emit() + return verification + + def set_credentials(self, username, api_key, is_logged=True): + self.user_input.setText(username) + self.api_input.setText(api_key) + + self.error_label.hide() + + self._not_invalid_input(self.ftsite_input) + self._not_invalid_input(self.user_input) + self._not_invalid_input(self.api_input) + + if is_logged is not None: + self.set_is_logged(is_logged) + + def check_url(self, url): + if url is not None: + url = url.strip("/ ") + + if not url: + self.set_error(( + "You need to specify a valid server URL, " + "for example https://server-name.ftrackapp.com" + )) return - verification = credentials.check_credentials(username, apiKey) + if "http" not in url: + if url.endswith("ftrackapp.com"): + url = "https://" + url + else: + url = "https://{}.ftrackapp.com".format(url) + try: + result = requests.get( + url, + # Old python API will not work with redirect. + allow_redirects=False + ) + except requests.exceptions.RequestException: + self.set_error( + "Specified URL could not be reached." + ) + return - if verification is True: - credentials.save_credentials(username, apiKey, self.is_event) - credentials.set_env(username, apiKey) - if self.parent is not None: - self.parent.loginChange() - self._close_widget() + if ( + result.status_code != 200 + or "FTRACK_VERSION" not in result.headers + ): + self.set_error( + "Specified URL does not lead to a valid Ftrack server." + ) + return + return url def closeEvent(self, event): event.ignore() From 15cb7393297f3ba30a50e2d610ddfd2cd7a1efe1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 12:45:16 +0200 Subject: [PATCH 544/947] ftrack module modified to be able handle new ftrack login dialog --- pype/modules/ftrack/tray/ftrack_module.py | 110 +++++++++++++--------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/pype/modules/ftrack/tray/ftrack_module.py b/pype/modules/ftrack/tray/ftrack_module.py index 674e8cbd4f..99f382b11e 100644 --- a/pype/modules/ftrack/tray/ftrack_module.py +++ b/pype/modules/ftrack/tray/ftrack_module.py @@ -2,7 +2,7 @@ import os import time import datetime import threading -from Qt import QtCore, QtWidgets +from Qt import QtCore, QtWidgets, QtGui import ftrack_api from ..ftrack_server.lib import check_ftrack_url @@ -10,7 +10,7 @@ from ..ftrack_server import socket_thread from ..lib import credentials from . import login_dialog -from pype.api import Logger +from pype.api import Logger, resources log = Logger().get_logger("FtrackModule", "ftrack") @@ -19,7 +19,7 @@ log = Logger().get_logger("FtrackModule", "ftrack") class FtrackModule: def __init__(self, main_parent=None, parent=None): self.parent = parent - self.widget_login = login_dialog.Login_Dialog_ui(self) + self.thread_action_server = None self.thread_socket_server = None self.thread_timer = None @@ -29,8 +29,22 @@ class FtrackModule: self.bool_action_thread_running = False self.bool_timer_event = False + self.widget_login = login_dialog.CredentialsDialog() + self.widget_login.login_changed.connect(self.on_login_change) + self.widget_login.logout_signal.connect(self.on_logout) + + self.action_credentials = None + self.icon_logged = QtGui.QIcon( + resources.get_resource("icons", "circle_green.png") + ) + self.icon_not_logged = QtGui.QIcon( + resources.get_resource("icons", "circle_orange.png") + ) + def show_login_widget(self): self.widget_login.show() + self.widget_login.activateWindow() + self.widget_login.raise_() def validate(self): validation = False @@ -39,9 +53,10 @@ class FtrackModule: ft_api_key = cred.get("api_key") validation = credentials.check_credentials(ft_user, ft_api_key) if validation: + self.widget_login.set_credentials(ft_user, ft_api_key) credentials.set_env(ft_user, ft_api_key) log.info("Connected to Ftrack successfully") - self.loginChange() + self.on_login_change() return validation @@ -60,15 +75,24 @@ class FtrackModule: return validation # Necessary - login_dialog works with this method after logging in - def loginChange(self): + def on_login_change(self): self.bool_logged = True + + self.action_credentials.setIcon(self.icon_logged) + self.action_credentials.setToolTip( + "Logged as user \"{}\"".format(self.widget_login.user_input.text()) + ) + self.set_menu_visibility() self.start_action_server() - def logout(self): + def on_logout(self): credentials.clear_credentials() self.stop_action_server() + self.action_credentials.setIcon(self.icon_not_logged) + self.action_credentials.setToolTip("Logged out") + log.info("Logged out of Ftrack") self.bool_logged = False self.set_menu_visibility() @@ -218,43 +242,45 @@ class FtrackModule: # Definition of Tray menu def tray_menu(self, parent_menu): # Menu for Tray App - self.menu = QtWidgets.QMenu('Ftrack', parent_menu) - self.menu.setProperty('submenu', 'on') - - # Actions - server - self.smActionS = self.menu.addMenu("Action server") - - self.aRunActionS = QtWidgets.QAction( - "Run action server", self.smActionS - ) - self.aResetActionS = QtWidgets.QAction( - "Reset action server", self.smActionS - ) - self.aStopActionS = QtWidgets.QAction( - "Stop action server", self.smActionS - ) - - self.aRunActionS.triggered.connect(self.start_action_server) - self.aResetActionS.triggered.connect(self.reset_action_server) - self.aStopActionS.triggered.connect(self.stop_action_server) - - self.smActionS.addAction(self.aRunActionS) - self.smActionS.addAction(self.aResetActionS) - self.smActionS.addAction(self.aStopActionS) + tray_menu = QtWidgets.QMenu("Ftrack", parent_menu) # Actions - basic - self.aLogin = QtWidgets.QAction("Login", self.menu) - self.aLogin.triggered.connect(self.validate) - self.aLogout = QtWidgets.QAction("Logout", self.menu) - self.aLogout.triggered.connect(self.logout) + action_credentials = QtWidgets.QAction("Credentials", tray_menu) + action_credentials.triggered.connect(self.show_login_widget) + if self.bool_logged: + icon = self.icon_logged + else: + icon = self.icon_not_logged + action_credentials.setIcon(icon) + tray_menu.addAction(action_credentials) + self.action_credentials = action_credentials - self.menu.addAction(self.aLogin) - self.menu.addAction(self.aLogout) + # Actions - server + tray_server_menu = tray_menu.addMenu("Action server") + self.action_server_run = QtWidgets.QAction( + "Run action server", tray_server_menu + ) + self.action_server_reset = QtWidgets.QAction( + "Reset action server", tray_server_menu + ) + self.action_server_stop = QtWidgets.QAction( + "Stop action server", tray_server_menu + ) + + self.action_server_run.triggered.connect(self.start_action_server) + self.action_server_reset.triggered.connect(self.reset_action_server) + self.action_server_stop.triggered.connect(self.stop_action_server) + + tray_server_menu.addAction(self.action_server_run) + tray_server_menu.addAction(self.action_server_reset) + tray_server_menu.addAction(self.action_server_stop) + + self.tray_server_menu = tray_server_menu self.bool_logged = False self.set_menu_visibility() - parent_menu.addMenu(self.menu) + parent_menu.addMenu(tray_menu) def tray_start(self): self.validate() @@ -264,19 +290,15 @@ class FtrackModule: # Definition of visibility of each menu actions def set_menu_visibility(self): - - self.smActionS.menuAction().setVisible(self.bool_logged) - self.aLogin.setVisible(not self.bool_logged) - self.aLogout.setVisible(self.bool_logged) - + self.tray_server_menu.menuAction().setVisible(self.bool_logged) if self.bool_logged is False: if self.bool_timer_event is True: self.stop_timer_thread() return - self.aRunActionS.setVisible(not self.bool_action_server_running) - self.aResetActionS.setVisible(self.bool_action_thread_running) - self.aStopActionS.setVisible(self.bool_action_server_running) + self.action_server_run.setVisible(not self.bool_action_server_running) + self.action_server_reset.setVisible(self.bool_action_thread_running) + self.action_server_stop.setVisible(self.bool_action_server_running) if self.bool_timer_event is False: self.start_timer_thread() From c49ad39965dfdf9d8cce447128ae5f399635d6c5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 12:46:21 +0200 Subject: [PATCH 545/947] more secure icon changes --- pype/modules/ftrack/tray/ftrack_module.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pype/modules/ftrack/tray/ftrack_module.py b/pype/modules/ftrack/tray/ftrack_module.py index 99f382b11e..0b011c5b33 100644 --- a/pype/modules/ftrack/tray/ftrack_module.py +++ b/pype/modules/ftrack/tray/ftrack_module.py @@ -78,10 +78,13 @@ class FtrackModule: def on_login_change(self): self.bool_logged = True - self.action_credentials.setIcon(self.icon_logged) - self.action_credentials.setToolTip( - "Logged as user \"{}\"".format(self.widget_login.user_input.text()) - ) + if self.action_credentials: + self.action_credentials.setIcon(self.icon_logged) + self.action_credentials.setToolTip( + "Logged as user \"{}\"".format( + self.widget_login.user_input.text() + ) + ) self.set_menu_visibility() self.start_action_server() @@ -90,8 +93,9 @@ class FtrackModule: credentials.clear_credentials() self.stop_action_server() - self.action_credentials.setIcon(self.icon_not_logged) - self.action_credentials.setToolTip("Logged out") + if self.action_credentials: + self.action_credentials.setIcon(self.icon_not_logged) + self.action_credentials.setToolTip("Logged out") log.info("Logged out of Ftrack") self.bool_logged = False From ca05176ce5d1becbf3972be6d87cf0b856bb5369 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 12:54:40 +0200 Subject: [PATCH 546/947] thread does not have stop method --- pype/modules/ftrack/tray/login_dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index 9ffd21fd30..b142f31891 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -232,7 +232,6 @@ class CredentialsDialog(QtWidgets.QDialog): # If there is an existing server thread running we need to stop it. if self._login_server_thread: - self._login_server_thread.stop() self._login_server_thread.join() self._login_server_thread = None From b98db04a8671abff9fd1f21f4f7cb47b224892c4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 13:01:03 +0200 Subject: [PATCH 547/947] close widget when successfully logged in --- pype/modules/ftrack/tray/login_dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index b142f31891..b703c4cd14 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -224,6 +224,8 @@ class CredentialsDialog(QtWidgets.QDialog): self.set_error( "We're unable to sign in to Ftrack with these credentials" ) + else: + self._close_widget() def _on_ftrack_login_clicked(self): url = self.check_url(self.ftsite_input.text()) @@ -251,6 +253,7 @@ class CredentialsDialog(QtWidgets.QDialog): )) else: self.set_is_logged(True) + self._close_widget() def login_with_credentials(self, username, api_key): verification = credentials.check_credentials(username, api_key) From 2f05d7cb197aa7c91ec0ac37585cbf36f882b373 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 13:08:37 +0200 Subject: [PATCH 548/947] fixed closing widget --- pype/modules/ftrack/tray/login_dialog.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pype/modules/ftrack/tray/login_dialog.py b/pype/modules/ftrack/tray/login_dialog.py index b703c4cd14..7730ee1609 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/pype/modules/ftrack/tray/login_dialog.py @@ -35,6 +35,8 @@ class CredentialsDialog(QtWidgets.QDialog): self.setMaximumSize(QtCore.QSize(self.SIZE_W + 100, self.SIZE_H + 100)) self.setStyleSheet(style.load_stylesheet()) + self.login_changed.connect(self._on_login) + self.ui_init() def ui_init(self): @@ -202,6 +204,10 @@ class CredentialsDialog(QtWidgets.QDialog): def _invalid_input(self, input_widget): input_widget.setStyleSheet("border: 1px solid red;") + def _on_login(self): + self.set_is_logged(True) + self._close_widget() + def _on_login_clicked(self): username = self.user_input.text().strip() api_key = self.api_input.text().strip() @@ -224,8 +230,6 @@ class CredentialsDialog(QtWidgets.QDialog): self.set_error( "We're unable to sign in to Ftrack with these credentials" ) - else: - self._close_widget() def _on_ftrack_login_clicked(self): url = self.check_url(self.ftsite_input.text()) @@ -251,9 +255,6 @@ class CredentialsDialog(QtWidgets.QDialog): "Somthing happened with Ftrack login." " Try enter Username and API key manually." )) - else: - self.set_is_logged(True) - self._close_widget() def login_with_credentials(self, username, api_key): verification = credentials.check_credentials(username, api_key) From f66d14e582d540bf262dc3d3d5cacf60f96bede0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 17:55:53 +0200 Subject: [PATCH 549/947] scrollers are a littlebit better --- .../config_setting/style/style.css | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 0099e6ed76..73715d892d 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -116,3 +116,118 @@ QPushButton[btn-type="expand-toggle"] { #ExpandingWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"] { border-color: #137cbd; } + +QScrollBar:horizontal { + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent #1d272f; + border-radius: 4px; + background-color: #1d272f; +} + +QScrollBar::handle:horizontal { + background-color: #61839e; + min-width: 5px; + border-radius: 4px; +} + +QScrollBar::add-line:horizontal { + margin: 0px 3px 0px 3px; + border-image: url(:/qss_icons/rc/right_arrow_disabled.png); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + margin: 0px 3px 0px 3px; + border-image: url(:/qss_icons/rc/left_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on { + border-image: url(:/qss_icons/rc/right_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { + border-image: url(:/qss_icons/rc/left_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { + background: none; +} + +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: none; +} + +QScrollBar:vertical { + background-color: #1d272f; + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent #1d272f; + border-radius: 4px; +} + +QScrollBar::handle:vertical { + background-color: #61839e; + min-height: 5px; + border-radius: 4px; +} + +QScrollBar::sub-line:vertical { + margin: 3px 0px 3px 0px; + border-image: url(:/qss_icons/rc/up_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical { + margin: 3px 0px 3px 0px; + border-image: url(:/qss_icons/rc/down_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on { + + border-image: url(:/qss_icons/rc/up_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + + +QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { + border-image: url(:/qss_icons/rc/down_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + background: none; +} + + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; +} From d1ffd17f5f925be45d66d499478ea112285418da Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 17:56:03 +0200 Subject: [PATCH 550/947] qmenu items are visible --- .../config_setting/config_setting/style/style.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 73715d892d..9bf8828920 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -5,6 +5,19 @@ QWidget { border-radius: 0px; } +QMenu { + border: 1px solid #555555; + background-color: #1d272f; +} + +QMenu::item { + padding: 5px 10px 5px 10px; +} + +QMenu::item:selected { + background-color: #202e3a; +} + QCheckBox::indicator { } QCheckBox::indicator:focus { From b274cfed16dc39c5945914f8e7bd8626872abf3c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 18:04:39 +0200 Subject: [PATCH 551/947] mae qmenu in same style --- pype/tools/config_setting/config_setting/style/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 9bf8828920..c5f1f33500 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -12,10 +12,12 @@ QMenu { QMenu::item { padding: 5px 10px 5px 10px; + border-left: 5px solid #313131; } QMenu::item:selected { - background-color: #202e3a; + border-left-color: #61839e; + background-color: #222d37; } QCheckBox::indicator { From e5960d7fa0fab9b294b611cd3b98ca2987564f78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 18:42:08 +0200 Subject: [PATCH 552/947] added validation for is_group --- .../config_setting/widgets/lib.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index c6379b4816..e7173f1c56 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -94,6 +94,19 @@ class SchemaMissingFileInfo(Exception): super(SchemaMissingFileInfo, self).__init__(msg) +class SchemeGroupHierarchyBug(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Items with attribute \"is_group\" can't have another item with" + " \"is_group\" attribute as child. Error happened for keys: [{}]" + ).format(", ".join(full_path_keys)) + super(SchemeGroupHierarchyBug, self).__init__(msg) + + def file_keys_from_schema(schema_data): output = [] keys = [] @@ -147,11 +160,55 @@ def validate_all_has_ending_file(schema_data, is_top=True): raise SchemaMissingFileInfo(invalid) +def validate_is_group_is_unique_in_hierarchy( + schema_data, any_parent_is_group=False, keys=None +): + is_top = keys is None + if keys is None: + keys = [] + + keyless = "key" not in schema_data + + if not keyless: + keys.append(schema_data["key"]) + + invalid = [] + is_group = schema_data.get("is_group") + if is_group and any_parent_is_group: + invalid.append(copy.deepcopy(keys)) + + if is_group: + any_parent_is_group = is_group + + children = schema_data.get("children") + if not children: + return invalid + + for child in children: + result = validate_is_group_is_unique_in_hierarchy( + child, any_parent_is_group, copy.deepcopy(keys) + ) + if not result: + continue + + invalid.extend(result) + + if invalid and is_group and keys not in invalid: + invalid.append(copy.deepcopy(keys)) + + if not is_top: + return invalid + + if invalid: + raise SchemeGroupHierarchyBug(invalid) + + def validate_schema(schema_data): # TODO validator for key uniquenes # TODO validator that is_group key is not before is_file child # TODO validator that is_group or is_file is not on child without key validate_all_has_ending_file(schema_data) + validate_is_group_is_unique_in_hierarchy(schema_data) def gui_schema(subfolder, main_schema_name): From 28c4a69ea13804b96744a3a56afeb5cd0a24ea7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 19:52:35 +0200 Subject: [PATCH 553/947] added ConfigWidget for reimplementing Qt methods --- .../config_setting/widgets/widgets.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index a15edf58ff..89f6782cfd 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui class ModifiedIntSpinBox(QtWidgets.QSpinBox): @@ -38,7 +38,43 @@ class ClickableWidget(QtWidgets.QLabel): super(ClickableWidget, self).mouseReleaseEvent(event) -class ExpandingWidget(QtWidgets.QWidget): +class ConfigWidget(QtWidgets.QWidget): + allow_actions = True + + def mouseReleaseEvent(self, event): + if self.allow_actions and event.button() == QtCore.Qt.RightButton: + menu = QtWidgets.QMenu() + + actions_mapping = {} + if self.child_modified: + action = QtWidgets.QAction("Discard changes") + actions_mapping[action] = self._discard_changes + menu.addAction(action) + + if ( + not self.any_parent_overriden() + and (self.is_overriden or self.child_overriden) + ): + # TODO better label + action = QtWidgets.QAction("Remove override") + actions_mapping[action] = self._remove_overrides + menu.addAction(action) + + if not actions_mapping: + action = QtWidgets.QAction("< No action >") + actions_mapping[action] = None + menu.addAction(action) + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + to_run = actions_mapping[result] + if to_run: + to_run() + return + super(ConfigWidget, self).mouseReleaseEvent(event) + + +class ExpandingWidget(ConfigWidget): def __init__(self, label, parent): super(ExpandingWidget, self).__init__(parent) self.setObjectName("ExpandingWidget") From d320924b9e9831d41fe49a204805e8c7ec9855b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 19:55:02 +0200 Subject: [PATCH 554/947] ConfigWidget and InputWidget renamed to ConfigObject and InputWidget --- .../config_setting/widgets/inputs.py | 72 ++++++------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3e344cd703..e30f437cd9 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1,6 +1,7 @@ import json from Qt import QtWidgets, QtCore, QtGui from .widgets import ( + ConfigWidget, ExpandingWidget, ModifiedIntSpinBox, ModifiedFloatSpinBox @@ -16,7 +17,7 @@ class SchemeGroupHierarchyBug(Exception): super(SchemeGroupHierarchyBug, self).__init__(msg) -class ConfigWidget: +class ConfigObject: default_state = "" _is_overriden = False _is_modified = False @@ -122,40 +123,8 @@ class ConfigWidget: "Method `hierarchical_style_update` not implemented!" ) - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.RightButton: - menu = QtWidgets.QMenu() - actions_mapping = {} - if self.child_modified: - action = QtWidgets.QAction("Discard changes") - actions_mapping[action] = self._discard_changes - menu.addAction(action) - - if ( - not self.any_parent_overriden() - and (self.is_overriden or self.child_overriden) - ): - # TODO better label - action = QtWidgets.QAction("Remove override") - actions_mapping[action] = self._remove_overrides - menu.addAction(action) - - if not actions_mapping: - action = QtWidgets.QAction("< No action >") - actions_mapping[action] = None - menu.addAction(action) - - result = menu.exec_(QtGui.QCursor.pos()) - if result: - to_run = actions_mapping[result] - if to_run: - to_run() - return - super(self.__class__, self).mouseReleaseEvent(event) - - -class InputWidget(ConfigWidget): +class InputObject(ConfigObject): def overrides(self): if not self.is_overriden: return NOT_SET, False @@ -200,7 +169,7 @@ class InputWidget(ConfigWidget): return -class BooleanWidget(QtWidgets.QWidget, InputWidget): +class BooleanWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -329,7 +298,7 @@ class BooleanWidget(QtWidgets.QWidget, InputWidget): return self.checkbox.isChecked() -class IntegerWidget(QtWidgets.QWidget, InputWidget): +class IntegerWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -446,7 +415,7 @@ class IntegerWidget(QtWidgets.QWidget, InputWidget): return self.int_input.value() -class FloatWidget(QtWidgets.QWidget, InputWidget): +class FloatWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -571,7 +540,7 @@ class FloatWidget(QtWidgets.QWidget, InputWidget): return self.float_input.value() -class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): +class TextSingleLineWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -580,7 +549,6 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): self._parent = parent self._as_widget = values is AS_WIDGET - any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group @@ -688,7 +656,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputWidget): return self.text_input.text() -class TextMultiLineWidget(QtWidgets.QWidget, InputWidget): +class TextMultiLineWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -880,7 +848,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.update_style(is_valid) -class RawJsonWidget(QtWidgets.QWidget, InputWidget): +class RawJsonWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -999,7 +967,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputWidget): return self.text_input.toPlainText() -class TextListItem(QtWidgets.QWidget, ConfigWidget): +class TextListItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1049,7 +1017,7 @@ class TextListItem(QtWidgets.QWidget, ConfigWidget): return self.text_input.text() -class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): +class TextListSubWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): @@ -1169,7 +1137,7 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigWidget): return output -class TextListWidget(QtWidgets.QWidget, InputWidget): +class TextListWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -1272,7 +1240,7 @@ class TextListWidget(QtWidgets.QWidget, InputWidget): return self.value_widget.config_value() -class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): +class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1380,7 +1348,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigWidget): return {key: value} -class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): +class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): @@ -1486,7 +1454,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigWidget): return output -class ModifiableDict(ExpandingWidget, InputWidget): +class ModifiableDict(ExpandingWidget, InputObject): # Should be used only for dictionary with one datatype as value # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) @@ -1584,7 +1552,7 @@ class ModifiableDict(ExpandingWidget, InputWidget): # Dictionaries -class DictExpandWidget(ExpandingWidget, ConfigWidget): +class DictExpandWidget(ExpandingWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__( @@ -1775,7 +1743,7 @@ class DictExpandWidget(ExpandingWidget, ConfigWidget): return {self.key: values}, self.is_group -class DictWidget(QtWidgets.QWidget, ConfigWidget): +class DictWidget(ConfigWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__( @@ -1981,9 +1949,10 @@ class DictWidget(QtWidgets.QWidget, ConfigWidget): return {self.key: values}, self.is_group -class DictInvisible(QtWidgets.QWidget, ConfigWidget): +class DictInvisible(ConfigWidget, ConfigObject): # TODO is not overridable by itself value_changed = QtCore.Signal(object) + allow_actions = False def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -2130,8 +2099,9 @@ class DictInvisible(QtWidgets.QWidget, ConfigWidget): # Proxy for form layout -class DictFormWidget(QtWidgets.QWidget, ConfigWidget): +class DictFormWidget(ConfigWidget, ConfigObject): value_changed = QtCore.Signal(object) + allow_actions = False def __init__( self, input_data, values, parent_keys, parent, label_widget=None From 7887cb3d7215379e532cc7f10188c8a1707832d8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 19:55:28 +0200 Subject: [PATCH 555/947] FormWIdget can handle mouse right clicks for its items --- .../config_setting/widgets/inputs.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e30f437cd9..64a5a90b45 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2098,6 +2098,12 @@ class DictInvisible(ConfigWidget, ConfigObject): return {self.key: values}, self.is_group +class FormLabel(QtWidgets.QLabel): + def __init__(self, *args, **kwargs): + super(FormLabel, self).__init__(*args, **kwargs) + self.item = None + + # Proxy for form layout class DictFormWidget(ConfigWidget, ConfigObject): value_changed = QtCore.Signal(object) @@ -2126,6 +2132,16 @@ class DictFormWidget(ConfigWidget, ConfigObject): for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + position = self.mapFromGlobal(QtGui.QCursor().pos()) + widget = self.childAt(position) + if widget and isinstance(widget, FormLabel): + widget.item.mouseReleaseEvent(event) + event.accept() + return + super(DictFormWidget, self).mouseReleaseEvent(event) + def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -2153,18 +2169,20 @@ class DictFormWidget(ConfigWidget, ConfigObject): klass = TypeToKlass.types.get(item_type) - label_widget = QtWidgets.QLabel(label) + label_widget = FormLabel(label, self) item = klass( child_configuration, values, self.keys, self, label_widget ) + label_widget.item = item + item.value_changed.connect(self._on_value_change) self.content_layout.addRow(label_widget, item) self.input_fields[key] = item return item def hierarchical_style_update(self): - for input_field in self.input_fields.items(): + for input_field in self.input_fields.values(): input_field.hierarchical_style_update() def item_value(self): From 1c40104d512837360ea6ea4a20703cc369195a9a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 20:01:56 +0200 Subject: [PATCH 556/947] removed SchemeGroupHierarchyBug as is already validated when loading schemas --- .../config_setting/widgets/inputs.py | 127 ++---------------- 1 file changed, 12 insertions(+), 115 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 64a5a90b45..09174b7c0e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -9,14 +9,6 @@ from .widgets import ( from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass -class SchemeGroupHierarchyBug(Exception): - def __init__(self, msg=None): - if not msg: - # TODO better message - msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" - super(SchemeGroupHierarchyBug, self).__init__(msg) - - class ConfigObject: default_state = "" _is_overriden = False @@ -175,21 +167,10 @@ class BooleanWidget(ConfigWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): - self._as_widget = values is AS_WIDGET self._parent = parent + self._as_widget = values is AS_WIDGET - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -307,18 +288,7 @@ class IntegerWidget(ConfigWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -424,18 +394,7 @@ class FloatWidget(ConfigWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -549,17 +508,7 @@ class TextSingleLineWidget(ConfigWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -665,18 +614,7 @@ class TextMultiLineWidget(ConfigWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -857,18 +795,7 @@ class RawJsonWidget(ConfigWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -1145,18 +1072,7 @@ class TextListWidget(ConfigWidget, InputObject): ): self._parent = parent - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None @@ -1469,16 +1385,9 @@ class ModifiableDict(ExpandingWidget, InputObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - - if not any_parent_is_group and not is_group: - is_group = True - self.any_parent_is_group = any_parent_is_group - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None super(ModifiableDict, self).__init__(input_data["label"], parent) @@ -1568,13 +1477,9 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - self.any_parent_is_group = any_parent_is_group - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None self._child_state = None @@ -1759,13 +1664,9 @@ class DictWidget(ConfigWidget, ConfigObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - self.any_parent_is_group = any_parent_is_group - self.is_group = is_group + self.is_group = input_data.get("is_group", False) self._state = None self._child_state = None @@ -1963,12 +1864,8 @@ class DictInvisible(ConfigWidget, ConfigObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - is_group = input_data.get("is_group", False) - if is_group and any_parent_is_group: - raise SchemeGroupHierarchyBug() - self.any_parent_is_group = any_parent_is_group - self.is_group = is_group + self.is_group = input_data.get("is_group", False) super(DictInvisible, self).__init__(parent) self.setObjectName("DictInvisible") From bb78d47d482afcd494bf27b8822cd05d53c235f2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 20:02:09 +0200 Subject: [PATCH 557/947] implemented discard changes for form layout --- pype/tools/config_setting/config_setting/widgets/inputs.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 09174b7c0e..d4806b4a5d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2039,6 +2039,13 @@ class DictFormWidget(ConfigWidget, ConfigObject): return super(DictFormWidget, self).mouseReleaseEvent(event) + def discard_changes(self): + for item in self.input_fields.values(): + item.discard_changes() + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + def _on_value_change(self, item=None): if self.ignore_value_changes: return From a295c6420f9756e79e8b9be2005b849049e8f0c5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 20:24:44 +0200 Subject: [PATCH 558/947] removed duplicated keys --- .../1_applications_gui_schema.json | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json index bbf74a8f3f..2e60ed360d 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json @@ -36,18 +36,6 @@ "type": "boolean", "key": "harmony_17", "label": "Harmony 17" - }, { - "type": "boolean", - "key": "houdini_16", - "label": "Houdini 16" - }, { - "type": "boolean", - "key": "houdini_17", - "label": "Houdini 17" - }, { - "type": "boolean", - "key": "houdini_18", - "label": "Houdini 18" }, { "type": "boolean", "key": "maya_2017", @@ -112,6 +100,10 @@ "type": "boolean", "key": "nukestudio_12.0", "label": "NukeStudio 12.0" + }, { + "type": "boolean", + "key": "houdini_16", + "label": "Houdini 16" }, { "type": "boolean", "key": "houdini_16.5", @@ -132,10 +124,6 @@ "type": "boolean", "key": "premiere_2020", "label": "Premiere 2020" - }, { - "type": "boolean", - "key": "premiere_2020", - "label": "Premiere 2020" }, { "type": "boolean", "key": "resolve_16", From 25404fbadceb0e3afe0e7167a3e623ff9de90733 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Aug 2020 20:28:52 +0200 Subject: [PATCH 559/947] implemented validation for duplicated keys --- .../config_setting/widgets/lib.py | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index e7173f1c56..fe4e514aaf 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -2,7 +2,7 @@ import os import json import copy from pype.api import config - +from queue import Queue OVERRIDEN_KEY = config.OVERRIDEN_KEY @@ -107,6 +107,21 @@ class SchemeGroupHierarchyBug(Exception): super(SchemeGroupHierarchyBug, self).__init__(msg) +class SchemaDuplicatedKeys(Exception): + def __init__(self, invalid): + items = [] + for key_path, keys in invalid.items(): + joined_keys = ", ".join([ + "\"{}\"".format(key) for key in keys + ]) + items.append("\"{}\" ({})".format(key_path, joined_keys)) + + msg = ( + "Schema items contain duplicated keys in one hierarchy level. {}" + ).format(" || ".join(items)) + super(SchemaDuplicatedKeys, self).__init__(msg) + + def file_keys_from_schema(schema_data): output = [] keys = [] @@ -203,12 +218,63 @@ def validate_is_group_is_unique_in_hierarchy( raise SchemeGroupHierarchyBug(invalid) +def validate_keys_are_unique(schema_data, keys=None): + is_top = keys is None + if keys is None: + keys = [schema_data["key"]] + else: + keys.append(schema_data["key"]) + + children = schema_data.get("children") + if not children: + return + + child_queue = Queue() + for child in children: + child_queue.put(child) + + child_inputs = [] + while not child_queue.empty(): + child = child_queue.get() + if "key" not in child: + _children = child.get("children") or [] + for _child in _children: + child_queue.put(_child) + else: + child_inputs.append(child) + + duplicated_keys = set() + child_keys = set() + for child in child_inputs: + key = child["key"] + if key in child_keys: + duplicated_keys.add(key) + else: + child_keys.add(key) + + invalid = {} + if duplicated_keys: + joined_keys = "/".join(keys) + invalid[joined_keys] = duplicated_keys + + for child in child_inputs: + result = validate_keys_are_unique(child, copy.deepcopy(keys)) + if result: + invalid.update(result) + + if not is_top: + return invalid + + if invalid: + raise SchemaDuplicatedKeys(invalid) + + def validate_schema(schema_data): - # TODO validator for key uniquenes # TODO validator that is_group key is not before is_file child # TODO validator that is_group or is_file is not on child without key validate_all_has_ending_file(schema_data) validate_is_group_is_unique_in_hierarchy(schema_data) + validate_keys_are_unique(schema_data) def gui_schema(subfolder, main_schema_name): From 850ab0a820698d21af891f7a6fc1f9e4a10ca5f6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 27 Aug 2020 11:01:46 +0100 Subject: [PATCH 560/947] Fix collect reviews - The code logic resulted in the last track review in the context being the review file for all shots. Comparing shot naming as well to isolate to correct clip instance. - Remove "- review" from label cause its already in subset. --- pype/plugins/nukestudio/publish/collect_reviews.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py index c158dee876..3167c66170 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -63,10 +63,14 @@ class CollectReviews(api.InstancePlugin): self.log.debug("Track item on plateMain") rev_inst = None for inst in instance.context[:]: - if inst.data["track"] in track: - rev_inst = inst - self.log.debug("Instance review: {}".format( - rev_inst.data["name"])) + if inst.data["track"] != track: + continue + + if inst.data["item"].name() != instance.data["item"].name(): + continue + + rev_inst = inst + break if rev_inst is None: raise RuntimeError(( @@ -82,7 +86,7 @@ class CollectReviews(api.InstancePlugin): ext = os.path.splitext(file)[-1][1:] # change label - instance.data["label"] = "{0} - {1} - ({2}) - review".format( + instance.data["label"] = "{0} - {1} - ({2})".format( instance.data['asset'], instance.data["subset"], ext ) From 7cdacafb8368b9aee8dd24e8647f1d5f8bde983f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 12:54:53 +0200 Subject: [PATCH 561/947] Moved PR from 2.x/develop --- .../modules/ftrack/actions/action_delivery.py | 364 +++++++++++++----- 1 file changed, 257 insertions(+), 107 deletions(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 7dbb7c65e8..86c3d604ee 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -1,5 +1,6 @@ import os import copy +import json import shutil import collections @@ -9,7 +10,7 @@ from bson.objectid import ObjectId from avalon import pipeline from avalon.vendor import filelink -from pype.api import Anatomy +from pype.api import Anatomy, config from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.modules.ftrack.lib.avalon_sync import CustAttrIdKey from pype.modules.ftrack.lib.io_nonsingleton import DbConnector @@ -41,36 +42,22 @@ class Delivery(BaseAction): items = [] item_splitter = {"type": "label", "value": "---"} - # Prepare component names for processing - components = None - project = None - for entity in entities: - if project is None: - project_id = None - for ent_info in entity["link"]: - if ent_info["type"].lower() == "project": - project_id = ent_info["id"] - break + project_entity = self.get_project_from_entity(entities[0]) + project_name = project_entity["full_name"] + self.db_con.install() + self.db_con.Session["AVALON_PROJECT"] = project_name + project_doc = self.db_con.find_one({"type": "project"}) + if not project_doc: + return { + "success": False, + "message": ( + "Didn't found project \"{}\" in avalon." + ).format(project_name) + } - if project_id is None: - project = entity["asset"]["parent"]["project"] - else: - project = session.query(( - "select id, full_name from Project where id is \"{}\"" - ).format(project_id)).one() + repre_names = self._get_repre_names(entities) + self.db_con.uninstall() - _components = set( - [component["name"] for component in entity["components"]] - ) - if components is None: - components = _components - continue - - components = components.intersection(_components) - if not components: - break - - project_name = project["full_name"] items.append({ "type": "hidden", "name": "__project_name__", @@ -93,7 +80,7 @@ class Delivery(BaseAction): skipped = False # Add message if there are any common components - if not components or not new_anatomies: + if not repre_names or not new_anatomies: skipped = True items.append({ "type": "label", @@ -106,7 +93,7 @@ class Delivery(BaseAction): "value": skipped }) - if not components: + if not repre_names: if len(entities) == 1: items.append({ "type": "label", @@ -143,12 +130,12 @@ class Delivery(BaseAction): "type": "label" }) - for component in components: + for repre_name in repre_names: items.append({ "type": "boolean", "value": False, - "label": component, - "name": component + "label": repre_name, + "name": repre_name }) items.append(item_splitter) @@ -198,27 +185,231 @@ class Delivery(BaseAction): "title": title } + def _get_repre_names(self, entities): + version_ids = self._get_interest_version_ids(entities) + repre_docs = self.db_con.find({ + "type": "representation", + "parent": {"$in": version_ids} + }) + return list(sorted(repre_docs.distinct("name"))) + + def _get_interest_version_ids(self, entities): + parent_ent_by_id = {} + subset_names = set() + version_nums = set() + for entity in entities: + asset = entity["asset"] + parent = asset["parent"] + parent_ent_by_id[parent["id"]] = parent + + subset_name = asset["name"] + subset_names.add(subset_name) + + version = entity["version"] + version_nums.add(version) + + asset_docs_by_ftrack_id = self._get_asset_docs(parent_ent_by_id) + subset_docs = self._get_subset_docs( + asset_docs_by_ftrack_id, subset_names, entities + ) + version_docs = self._get_version_docs( + asset_docs_by_ftrack_id, subset_docs, version_nums, entities + ) + + return [version_doc["_id"] for version_doc in version_docs] + + def _get_version_docs( + self, asset_docs_by_ftrack_id, subset_docs, version_nums, entities + ): + subset_docs_by_id = { + subset_doc["_id"]: subset_doc + for subset_doc in subset_docs + } + version_docs = list(self.db_con.find({ + "type": "version", + "parent": {"$in": list(subset_docs_by_id.keys())}, + "name": {"$in": list(version_nums)} + })) + version_docs_by_parent_id = collections.defaultdict(dict) + for version_doc in version_docs: + subset_doc = subset_docs_by_id[version_doc["parent"]] + + asset_id = subset_doc["parent"] + subset_name = subset_doc["name"] + version = version_doc["name"] + if version_docs_by_parent_id[asset_id].get(subset_name) is None: + version_docs_by_parent_id[asset_id][subset_name] = {} + + version_docs_by_parent_id[asset_id][subset_name][version] = ( + version_doc + ) + + filtered_versions = [] + for entity in entities: + asset = entity["asset"] + + parent = asset["parent"] + asset_doc = asset_docs_by_ftrack_id[parent["id"]] + + subsets_by_name = version_docs_by_parent_id.get(asset_doc["_id"]) + if not subsets_by_name: + continue + + subset_name = asset["name"] + version_docs_by_version = subsets_by_name.get(subset_name) + if not version_docs_by_version: + continue + + version = entity["version"] + version_doc = version_docs_by_version.get(version) + if version_doc: + filtered_versions.append(version_doc) + return filtered_versions + + def _get_subset_docs( + self, asset_docs_by_ftrack_id, subset_names, entities + ): + asset_doc_ids = list() + for asset_doc in asset_docs_by_ftrack_id.values(): + asset_doc_ids.append(asset_doc["_id"]) + + subset_docs = list(self.db_con.find({ + "type": "subset", + "parent": {"$in": asset_doc_ids}, + "name": {"$in": list(subset_names)} + })) + subset_docs_by_parent_id = collections.defaultdict(dict) + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + subset_name = subset_doc["name"] + subset_docs_by_parent_id[asset_id][subset_name] = subset_doc + + filtered_subsets = [] + for entity in entities: + asset = entity["asset"] + + parent = asset["parent"] + asset_doc = asset_docs_by_ftrack_id[parent["id"]] + + subsets_by_name = subset_docs_by_parent_id.get(asset_doc["_id"]) + if not subsets_by_name: + continue + + subset_name = asset["name"] + subset_doc = subsets_by_name.get(subset_name) + if subset_doc: + filtered_subsets.append(subset_doc) + return filtered_subsets + + def _get_asset_docs(self, parent_ent_by_id): + asset_docs = list(self.db_con.find({ + "type": "asset", + "data.ftrackId": {"$in": list(parent_ent_by_id.keys())} + })) + asset_docs_by_ftrack_id = { + asset_doc["data"]["ftrackId"]: asset_doc + for asset_doc in asset_docs + } + + entities_by_mongo_id = {} + entities_by_names = {} + for ftrack_id, entity in parent_ent_by_id.items(): + if ftrack_id not in asset_docs_by_ftrack_id: + parent_mongo_id = entity["custom_attributes"].get( + CustAttrIdKey + ) + if parent_mongo_id: + entities_by_mongo_id[ObjectId(parent_mongo_id)] = entity + else: + entities_by_names[entity["name"]] = entity + + expressions = [] + if entities_by_mongo_id: + expression = { + "type": "asset", + "_id": {"$in": list(entities_by_mongo_id.keys())} + } + expressions.append(expression) + + if entities_by_names: + expression = { + "type": "asset", + "name": {"$in": list(entities_by_names.keys())} + } + expressions.append(expression) + + if expressions: + if len(expressions) == 1: + filter = expressions[0] + else: + filter = {"$or": expressions} + + asset_docs = self.db_con.find(filter) + for asset_doc in asset_docs: + if asset_doc["_id"] in entities_by_mongo_id: + entity = entities_by_mongo_id[asset_doc["_id"]] + asset_docs_by_ftrack_id[entity["id"]] = asset_doc + + elif asset_doc["name"] in entities_by_names: + entity = entities_by_names[asset_doc["name"]] + asset_docs_by_ftrack_id[entity["id"]] = asset_doc + + return asset_docs_by_ftrack_id + def launch(self, session, entities, event): if "values" not in event["data"]: return - self.report_items = collections.defaultdict(list) - values = event["data"]["values"] skipped = values.pop("__skipped__") if skipped: return None - component_names = [] + user_id = event["source"]["user"]["id"] + user_entity = session.query("User where id is ".format(user_id)).one() + + job = session.create("Job", { + "user": user_entity, + "status": "running", + "data": json.dumps({ + "description": "Delivery processing." + }) + }) + session.commit() + + try: + self.db_con.install() + self.real_launch(session, entities, event) + job["status"] = "done" + + except Exception: + self.log.warning( + "Failed during processing delivery action.", + exc_info=True + ) + + finally: + if job["status"] != "done": + job["status"] = "failed" + session.commit() + self.db_con.uninstall() + + def real_launch(self, session, entities, event): + self.log.info("Delivery action just started.") + report_items = collections.defaultdict(list) + + values = event["data"]["values"] + location_path = values.pop("__location_path__") anatomy_name = values.pop("__new_anatomies__") project_name = values.pop("__project_name__") + repre_names = [] for key, value in values.items(): if value is True: - component_names.append(key) + repre_names.append(key) - if not component_names: + if not repre_names: return { "success": True, "message": "Not selected components to deliver." @@ -230,64 +421,15 @@ class Delivery(BaseAction): if not os.path.exists(location_path): os.makedirs(location_path) - self.db_con.install() self.db_con.Session["AVALON_PROJECT"] = project_name - repres_to_deliver = [] - for entity in entities: - asset = entity["asset"] - subset_name = asset["name"] - version = entity["version"] - - parent = asset["parent"] - parent_mongo_id = parent["custom_attributes"].get(CustAttrIdKey) - if parent_mongo_id: - parent_mongo_id = ObjectId(parent_mongo_id) - else: - asset_ent = self.db_con.find_one({ - "type": "asset", - "data.ftrackId": parent["id"] - }) - if not asset_ent: - ent_path = "/".join( - [ent["name"] for ent in parent["link"]] - ) - msg = "Not synchronized entities to avalon" - self.report_items[msg].append(ent_path) - self.log.warning("{} <{}>".format(msg, ent_path)) - continue - - parent_mongo_id = asset_ent["_id"] - - subset_ent = self.db_con.find_one({ - "type": "subset", - "parent": parent_mongo_id, - "name": subset_name - }) - - version_ent = self.db_con.find_one({ - "type": "version", - "name": version, - "parent": subset_ent["_id"] - }) - - repre_ents = self.db_con.find({ - "type": "representation", - "parent": version_ent["_id"] - }) - - repres_by_name = {} - for repre in repre_ents: - repre_name = repre["name"] - repres_by_name[repre_name] = repre - - for component in entity["components"]: - comp_name = component["name"] - if comp_name not in component_names: - continue - - repre = repres_by_name.get(comp_name) - repres_to_deliver.append(repre) + self.log.debug("Collecting representations to process.") + version_ids = self._get_interest_version_ids(entities) + repres_to_deliver = list(self.db_con.find({ + "type": "representation", + "parent": {"$in": version_ids}, + "name": {"$in": repre_names} + })) anatomy = Anatomy(project_name) @@ -304,9 +446,17 @@ class Delivery(BaseAction): for name in root_names: format_dict["root"][name] = location_path + datetime_data = config.get_datetime_data() for repre in repres_to_deliver: + source_path = repre.get("data", {}).get("path") + debug_msg = "Processing representation {}".format(repre["_id"]) + if source_path: + debug_msg += " with published path {}.".format(source_path) + self.log.debug(debug_msg) + # Get destination repre path anatomy_data = copy.deepcopy(repre["context"]) + anatomy_data.update(datetime_data) anatomy_filled = anatomy.format_all(anatomy_data) test_path = anatomy_filled["delivery"][anatomy_name] @@ -333,7 +483,7 @@ class Delivery(BaseAction): "- Invalid value DataType: \"{}\"
" ).format(str(repre["_id"]), keys) - self.report_items[msg].append(sub_msg) + report_items[msg].append(sub_msg) self.log.warning( "{} Representation: \"{}\" Filled: <{}>".format( msg, str(repre["_id"]), str(test_path) @@ -355,20 +505,19 @@ class Delivery(BaseAction): anatomy, anatomy_name, anatomy_data, - format_dict + format_dict, + report_items ) - if not frame: self.process_single_file(*args) else: self.process_sequence(*args) - self.db_con.uninstall() - - return self.report() + return self.report(report_items) def process_single_file( - self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict + self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict, + report_items ): anatomy_filled = anatomy.format(anatomy_data) if format_dict: @@ -384,7 +533,8 @@ class Delivery(BaseAction): self.copy_file(repre_path, delivery_path) def process_sequence( - self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict + self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict, + report_items ): dir_path, file_name = os.path.split(str(repre_path)) @@ -398,7 +548,7 @@ class Delivery(BaseAction): if not file_name_items: msg = "Source file was not found" - self.report_items[msg].append(repre_path) + report_items[msg].append(repre_path) self.log.warning("{} <{}>".format(msg, repre_path)) return @@ -418,7 +568,7 @@ class Delivery(BaseAction): if src_collection is None: # TODO log error! msg = "Source collection of files was not found" - self.report_items[msg].append(repre_path) + report_items[msg].append(repre_path) self.log.warning("{} <{}>".format(msg, repre_path)) return @@ -491,10 +641,10 @@ class Delivery(BaseAction): except OSError: shutil.copyfile(src_path, dst_path) - def report(self): + def report(self, report_items): items = [] title = "Delivery report" - for msg, _items in self.report_items.items(): + for msg, _items in report_items.items(): if not _items: continue From 4466db6e1242091946f37aa58acf3dc64abb78e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 14:02:38 +0200 Subject: [PATCH 562/947] fix user id missing formatting brackets --- pype/modules/ftrack/actions/action_delivery.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 86c3d604ee..663a81aad4 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -366,7 +366,9 @@ class Delivery(BaseAction): return None user_id = event["source"]["user"]["id"] - user_entity = session.query("User where id is ".format(user_id)).one() + user_entity = session.query( + "User where id is {}".format(user_id) + ).one() job = session.create("Job", { "user": user_entity, From 1c3e8c38fed53420866d24354d13fc4dfab73440 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 27 Aug 2020 14:39:44 +0200 Subject: [PATCH 563/947] update version.py --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index 0d4f03098b..9e1a271244 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.11.5" +__version__ = "2.11.8" From e074bc7733fa9464bbb0abef8f4011f56dcba6b3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:01:19 +0200 Subject: [PATCH 564/947] added hovering actions --- .../config_setting/config_setting/style/style.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index c5f1f33500..9478892b60 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -119,16 +119,29 @@ QPushButton[btn-type="expand-toggle"] { border-right-width: 0px; border-top-width: 0px; } +#ExpandingWidget:hover, #ModifiableDict:hover, #DictWidget:hover { + border-color: #62839d; +} + #ExpandingWidget[state="child-modified"], #ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { + border-color: #106aa2; +} +#ExpandingWidget[state="child-modified"]:hover, #ModifiableDict[state="child-modified"]:hover, #DictWidget[state="child-modified"]:hover { border-color: #137cbd; } #ExpandingWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"] { + border-color: #e67300; +} +#ExpandingWidget[state="child-overriden"]:hover, #ModifiableDict[state="child-overriden"]:hover, #DictWidget[state="child-overriden"]:hover { border-color: #ff8c1a; } #ExpandingWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"] { + border-color: #106aa2; +} +#ExpandingWidget[state="child-overriden-modified"]:hover, #ModifiableDict[state="child-overriden-modified"]:hover, #DictWidget[state="child-overriden-modified"]:hover { border-color: #137cbd; } From ae30e3c3befb9fecc32bc823ad52c8229d48f814 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:07:06 +0200 Subject: [PATCH 565/947] fixed raw json item value --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d4806b4a5d..8036eb5e1b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -777,6 +777,10 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): self.updateGeometry() super(RawJsonInput, self).resizeEvent(event) + def item_value(self): + value = self.value() + return json.loads(value) + def value(self): return self.toPlainText() @@ -891,7 +895,7 @@ class RawJsonWidget(ConfigWidget, InputObject): widget.style().polish(widget) def item_value(self): - return self.text_input.toPlainText() + return self.text_input.item_value() class TextListItem(QtWidgets.QWidget, ConfigObject): From b8ea2eea3a153f5abe0624735b445be50a14941c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:12:54 +0200 Subject: [PATCH 566/947] fixed text list items --- pype/tools/config_setting/config_setting/widgets/inputs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8036eb5e1b..67f1ac81ec 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -977,12 +977,13 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): self.override_value = NOT_SET def set_value(self, value, *, global_value=False): - for input_field in self.input_fields: - self.remove_row(input_field) - + previous_inputs = tuple(self.input_fields) for item_text in value: self.add_row(text=item_text) + for input_field in previous_inputs: + self.remove_row(input_field) + if global_value: self.global_value = value self.start_value = self.item_value() From 3c71ae5d2a28546c7e523cf9f3042cc2429ae589 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:21:36 +0200 Subject: [PATCH 567/947] raw json works better --- .../config_setting/widgets/inputs.py | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 67f1ac81ec..d800f8c635 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -709,6 +709,7 @@ class TextMultiLineWidget(ConfigWidget, InputObject): class RawJsonInput(QtWidgets.QPlainTextEdit): + value_changed = QtCore.Signal(object) tab_length = 4 def __init__(self, *args, **kwargs): @@ -720,7 +721,9 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): ).horizontalAdvance(" ") * self.tab_length ) + self._state = None self.is_valid = None + self.textChanged.connect(self._on_value_change) def sizeHint(self): document = self.document() @@ -742,13 +745,9 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): value = json.dumps(value, indent=4) self.setPlainText(value) - def setPlainText(self, *args, **kwargs): - super(RawJsonInput, self).setPlainText(*args, **kwargs) - self.validate() - - def focusOutEvent(self, event): - super(RawJsonInput, self).focusOutEvent(event) + def _on_value_change(self): self.validate() + self.value_changed.emit(self) def validate_value(self, value): if isinstance(value, str) and not value: @@ -760,16 +759,18 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): except Exception: return False - def update_style(self, is_valid=None): - if is_valid is None: + def update_style(self): + if self.is_valid is None: return self.validate() - if is_valid != self.is_valid: - self.is_valid = is_valid - if is_valid: - state = "" - else: - state = "invalid" + if self.is_valid: + state = "" + else: + state = "invalid" + + if self._state is None or self._state != state: + self._state = state + self.setProperty("state", state) self.style().polish(self) @@ -778,16 +779,12 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): super(RawJsonInput, self).resizeEvent(event) def item_value(self): - value = self.value() - return json.loads(value) - - def value(self): - return self.toPlainText() + return json.loads(self.toPlainText()) def validate(self): - value = self.value() - is_valid = self.validate_value(value) - self.update_style(is_valid) + value = self.toPlainText() + self.is_valid = self.validate_value(value) + self.update_style() class RawJsonWidget(ConfigWidget, InputObject): @@ -838,7 +835,7 @@ class RawJsonWidget(ConfigWidget, InputObject): self.start_value = self.item_value() self.override_value = NOT_SET - self.text_input.textChanged.connect(self._on_value_change) + self.text_input.value_changed.connect(self._on_value_change) def set_value(self, value, *, global_value=False): self.text_input.set_value(value) @@ -871,7 +868,11 @@ class RawJsonWidget(ConfigWidget, InputObject): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.global_value + if self.text_input.is_valid: + self._is_modified = self.item_value() != self.global_value + else: + self._is_modified = True + if self.is_overidable: self._is_overriden = True From 9af852b0cfd21eb03e9812dffe2fa83fd32e0737 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:24:34 +0200 Subject: [PATCH 568/947] invalid looks better --- pype/tools/config_setting/config_setting/style/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 9478892b60..ce014f0768 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -93,7 +93,8 @@ QPushButton[btn-type="expand-toggle"] { } #RawJsonInput[state="invalid"] { - border-left-color: #ff5511; + border-color: #ab2e46; + border-width: 2px; } #DictKey[state="modified"] { From 3fdcff6185771979966f47348e27e25d65f04f78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:28:11 +0200 Subject: [PATCH 569/947] reset state on set value in json raw input --- pype/tools/config_setting/config_setting/widgets/inputs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d800f8c635..6568543799 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -741,6 +741,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): return value def set_value(self, value, *, global_value=False): + self._state = None if not isinstance(value, str): value = json.dumps(value, indent=4) self.setPlainText(value) From 39a1a560aa485a456eb7d4435a6686fdee780f7e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 16:41:45 +0200 Subject: [PATCH 570/947] fixed json files with list inside --- pype/tools/config_setting/config_setting/widgets/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index c0246dd8a2..d5f9a05aea 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -469,7 +469,10 @@ class ProjectWidget(QtWidgets.QWidget): new_values = all_values for key in key_sequence: new_values = new_values[key] - origin_values.update(new_values) + if isinstance(new_values, dict): + origin_values.update(new_values) + else: + origin_values = new_values output_path = os.path.join( config.PROJECT_PRESETS_PATH, subpath From 5fe6a1e25d5e624980dd4d8a7577e04e586f5e5a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 17:08:53 +0200 Subject: [PATCH 571/947] fixed list input --- pype/tools/config_setting/config_setting/widgets/inputs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 6568543799..c7d02471a7 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1096,8 +1096,9 @@ class TextListWidget(ConfigWidget, InputObject): layout.addWidget(label_widget) self.label_widget = label_widget + self.key = input_data["key"] keys = list(parent_keys) - keys.append(input_data["key"]) + keys.append(self.key) self.keys = keys self.value_widget = TextListSubWidget( @@ -1107,7 +1108,7 @@ class TextListWidget(ConfigWidget, InputObject): self.value_widget.value_changed.connect(self._on_value_change) # self.value_widget.se - self.key = input_data["key"] + layout.addWidget(self.value_widget) self.setLayout(layout) @@ -1160,7 +1161,7 @@ class TextListWidget(ConfigWidget, InputObject): self.label_widget.style().polish(self.label_widget) def item_value(self): - return self.value_widget.config_value() + return self.value_widget.item_value() class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): From 66f99b321194493a5933be3fa5ac18adc745daa7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 17:46:41 +0200 Subject: [PATCH 572/947] list item does not have focus on btns --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c7d02471a7..4cba89f251 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -914,6 +914,8 @@ class TextListItem(QtWidgets.QWidget, ConfigObject): self.text_input = QtWidgets.QLineEdit() self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") + self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) self.add_btn.setProperty("btn-type", "text-list") self.remove_btn.setProperty("btn-type", "text-list") From 893173e1b8511d4f89754c5b53897eade89687e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 17:46:57 +0200 Subject: [PATCH 573/947] list items has right order --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4cba89f251..d353b6e53e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1037,6 +1037,12 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): self.layout().insertWidget(row, item_widget) self.input_fields.insert(row, item_widget) + previous_input = None + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder(previous_input, input_field.text_input) + previous_input = input_field.text_input + # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if text is not None: From 9fbdfcc1cf4a87e2a84ff85c18ff027b3ed7ad60 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 17:50:16 +0200 Subject: [PATCH 574/947] fixed tab order of key->value for modifieble dict item --- pype/tools/config_setting/config_setting/widgets/inputs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d353b6e53e..74505bebca 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1187,7 +1187,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): ItemKlass = TypeToKlass.types[object_type] - self.key_input = QtWidgets.QLineEdit() + self.key_input = QtWidgets.QLineEdit(self) self.key_input.setObjectName("DictKey") self.value_input = ItemKlass( @@ -1200,6 +1200,9 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") + self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.add_btn.setProperty("btn-type", "text-list") self.remove_btn.setProperty("btn-type", "text-list") From a3b6d64004bb249ab6524e4c3dd4bdfdc68bae07 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 27 Aug 2020 18:22:05 +0200 Subject: [PATCH 575/947] fixed most issues with tab order in modifiable dict --- .../config_setting/widgets/inputs.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 74505bebca..172b93e4ba 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -181,6 +181,9 @@ class BooleanWidget(ConfigWidget, InputObject): layout.setSpacing(5) self.checkbox = QtWidgets.QCheckBox() + + self.setFocusProxy(self.checkbox) + self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) if not self._as_widget and not label_widget: label = input_data["label"] @@ -300,6 +303,8 @@ class IntegerWidget(ConfigWidget, InputObject): self.int_input = ModifiedIntSpinBox() + self.setFocusProxy(self.int_input) + if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) @@ -406,6 +411,8 @@ class FloatWidget(ConfigWidget, InputObject): self.float_input = ModifiedFloatSpinBox() + self.setFocusProxy(self.float_input) + decimals = input_data.get("decimals", 5) maximum = input_data.get("maximum") minimum = input_data.get("minimum") @@ -520,6 +527,8 @@ class TextSingleLineWidget(ConfigWidget, InputObject): self.text_input = QtWidgets.QLineEdit() + self.setFocusProxy(self.text_input) + if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) @@ -625,6 +634,9 @@ class TextMultiLineWidget(ConfigWidget, InputObject): layout.setSpacing(5) self.text_input = QtWidgets.QPlainTextEdit() + + self.setFocusProxy(self.text_input) + if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) @@ -813,6 +825,8 @@ class RawJsonWidget(ConfigWidget, InputObject): QtWidgets.QSizePolicy.MinimumExpanding ) + self.setFocusProxy(self.text_input) + if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) @@ -1211,6 +1225,8 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) + self.setFocusProxy(self.value_input) + self.add_btn.setFixedSize(self._btn_size, self._btn_size) self.remove_btn.setFixedSize(self._btn_size, self._btn_size) self.add_btn.clicked.connect(self.on_add_clicked) @@ -1352,6 +1368,17 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): self.layout().insertWidget(row, item_widget) self.input_fields.insert(row, item_widget) + previous_input = None + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.key_input + ) + previous_input = input_field.value_input.focusProxy() + self.setTabOrder( + input_field.key_input, previous_input + ) + # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: From 401d1ad6132d7fd4dcc781150e41ca7785f9a73a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 10:04:04 +0200 Subject: [PATCH 576/947] make sure ignore_value_changes will raise proper error --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 172b93e4ba..050966dd63 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -38,6 +38,10 @@ class ConfigObject: @property def ignore_value_changes(self): + if not hasattr(self, "_parent"): + raise NotImplementedError( + "Object {} does not have `_parent` attribute".format(self) + ) return self._parent.ignore_value_changes @ignore_value_changes.setter From e2061c230f8023e0e150ec65afbb793bb6294248 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 10:04:24 +0200 Subject: [PATCH 577/947] set parenting to real input widgets --- .../config_setting/config_setting/widgets/inputs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 050966dd63..8f4d15912d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -184,7 +184,7 @@ class BooleanWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.checkbox = QtWidgets.QCheckBox() + self.checkbox = QtWidgets.QCheckBox(self) self.setFocusProxy(self.checkbox) @@ -305,7 +305,7 @@ class IntegerWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.int_input = ModifiedIntSpinBox() + self.int_input = ModifiedIntSpinBox(self) self.setFocusProxy(self.int_input) @@ -413,7 +413,7 @@ class FloatWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.float_input = ModifiedFloatSpinBox() + self.float_input = ModifiedFloatSpinBox(self) self.setFocusProxy(self.float_input) @@ -529,7 +529,7 @@ class TextSingleLineWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.text_input = QtWidgets.QLineEdit() + self.text_input = QtWidgets.QLineEdit(self) self.setFocusProxy(self.text_input) @@ -637,7 +637,7 @@ class TextMultiLineWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.text_input = QtWidgets.QPlainTextEdit() + self.text_input = QtWidgets.QPlainTextEdit(self) self.setFocusProxy(self.text_input) From 3edb2f4080f0bfadbd41732b8f32eec673afe458 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 10:05:30 +0200 Subject: [PATCH 578/947] TextListWidget changed to ListWidget --- .../config_setting/widgets/inputs.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8f4d15912d..67e44023e7 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1097,7 +1097,7 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): return output -class TextListWidget(ConfigWidget, InputObject): +class ListWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -1109,8 +1109,8 @@ class TextListWidget(ConfigWidget, InputObject): self._state = None - super(TextListWidget, self).__init__(parent) - self.setObjectName("TextListWidget") + super(ListWidget, self).__init__(parent) + self.setObjectName("ListWidget") layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -1127,17 +1127,16 @@ class TextListWidget(ConfigWidget, InputObject): keys.append(self.key) self.keys = keys - self.value_widget = TextListSubWidget( + self.value_widget = ListSubWidget( input_data, values, parent_keys, self ) self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - self.value_widget.value_changed.connect(self._on_value_change) - - # self.value_widget.se layout.addWidget(self.value_widget) self.setLayout(layout) + self.value_widget.value_changed.connect(self._on_value_change) + @property def start_value(self): return self.value_widget.start_value @@ -2178,4 +2177,4 @@ TypeToKlass.types["dict"] = DictWidget TypeToKlass.types["dict-expanding"] = DictExpandWidget TypeToKlass.types["dict-form"] = DictFormWidget TypeToKlass.types["dict-invisible"] = DictInvisible -TypeToKlass.types["list-text"] = TextListWidget +TypeToKlass.types["list"] = ListWidget From 4cfc0a7c1210024c709fe56c3b9cd45548d87731 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 10:06:46 +0200 Subject: [PATCH 579/947] TextListItem changed to ListItem --- .../config_setting/widgets/inputs.py | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 67e44023e7..e10a4a841d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -918,37 +918,50 @@ class RawJsonWidget(ConfigWidget, InputObject): return self.text_input.item_value() -class TextListItem(QtWidgets.QWidget, ConfigObject): +class ListItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, parent): - super(TextListItem, self).__init__(parent) + def __init__(self, object_type, parent): + self._parent = parent + + super(ListItem, self).__init__(parent) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) - self.text_input = QtWidgets.QLineEdit() + ItemKlass = TypeToKlass.types[object_type] + self.value_input = ItemKlass( + {}, + AS_WIDGET, + [], + self, + None + ) + self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") + self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.setProperty("btn-type", "text-list") self.remove_btn.setProperty("btn-type", "text-list") - layout.addWidget(self.text_input, 1) + layout.addWidget(self.value_input, 1) layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) - self.add_btn.setFixedSize(self._btn_size, self._btn_size) - self.remove_btn.setFixedSize(self._btn_size, self._btn_size) self.add_btn.clicked.connect(self.on_add_clicked) self.remove_btn.clicked.connect(self.on_remove_clicked) - self.text_input.textChanged.connect(self._on_value_change) + self.value_input.value_changed.connect(self._on_value_change) + self.value_input.item_value() self.is_single = False def _on_value_change(self, item=None): @@ -962,12 +975,12 @@ class TextListItem(QtWidgets.QWidget, ConfigObject): def on_remove_clicked(self): if self.is_single: - self.text_input.setText("") + self.value_input.clear() else: self.parent().remove_row(self) def config_value(self): - return self.text_input.text() + return self.value_input.item_value() class TextListSubWidget(QtWidgets.QWidget, ConfigObject): From d82118cd8e8cc869ca04481d15a88e71fecb0373 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 10:07:06 +0200 Subject: [PATCH 580/947] TextListSubWidget changed to ListSubWidget --- .../config_setting/widgets/inputs.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e10a4a841d..2f0d85fd4e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -983,12 +983,14 @@ class ListItem(QtWidgets.QWidget, ConfigObject): return self.value_input.item_value() -class TextListSubWidget(QtWidgets.QWidget, ConfigObject): +class ListSubWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): - super(TextListSubWidget, self).__init__(parent) - self.setObjectName("TextListSubWidget") + self._parent = parent + + super(ListSubWidget, self).__init__(parent) + self.setObjectName("ListSubWidget") layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 5, 0, 5) @@ -996,7 +998,7 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): self.setLayout(layout) self.input_fields = [] - self.add_row() + self.object_type = input_data["object_type"] self.key = input_data["key"] keys = list(parent_keys) @@ -1005,7 +1007,11 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): value = self.value_from_values(values) if value is not NOT_SET: - self.set_value(value) + for item_value in value: + self.add_row(value=item_value) + + if self.count() == 0: + self.add_row() self.global_value = value self.start_value = self.item_value() @@ -1013,8 +1019,8 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): def set_value(self, value, *, global_value=False): previous_inputs = tuple(self.input_fields) - for item_text in value: - self.add_row(text=item_text) + for item_value in value: + self.add_row(value=item_value) for input_field in previous_inputs: self.remove_row(input_field) @@ -1047,9 +1053,9 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): def count(self): return len(self.input_fields) - def add_row(self, row=None, text=None): + def add_row(self, row=None, value=None): # Create new item - item_widget = TextListItem(self) + item_widget = ListItem(self.object_type, self) # Set/unset if new item is single item current_count = self.count() @@ -1071,13 +1077,15 @@ class TextListSubWidget(QtWidgets.QWidget, ConfigObject): previous_input = None for input_field in self.input_fields: if previous_input is not None: - self.setTabOrder(previous_input, input_field.text_input) - previous_input = input_field.text_input + self.setTabOrder( + previous_input, input_field.value_input.focusProxy() + ) + previous_input = input_field.value_input.focusProxy() # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` - if text is not None: - item_widget.text_input.setText(text) + if value is not None: + item_widget.value_input.set_value(value, global_value=True) else: self._on_value_change() self.parent().updateGeometry() From 710c97f9a1ee0af00a66c5f0e9cd275b6db21df1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 10:08:25 +0200 Subject: [PATCH 581/947] use "list" instead of "list-text" --- .../projects_schema/1_plugins_gui_schema.json | 12 ++++++++---- .../studio_schema/0_studio_gui_schema.json | 10 +++++----- .../studio_schema/1_intents_gui_schema.json | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index e6582d82b1..9f40c7871b 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -88,7 +88,8 @@ "label": "Note with intent template", "default": "{intent}: {comment}" }, { - "type": "list-text", + "type": "list", + "object_type": "text-singleline", "key": "note_labels", "label": "Note labels", "default": [] @@ -132,12 +133,14 @@ "key": "ffmpeg_args", "children": [ { - "type": "list-text", + "type": "list", + "object_type": "text-singleline", "key": "input", "label": "FFmpeg input arguments", "default": [] }, { - "type": "list-text", + "type": "list", + "object_type": "text-singleline", "key": "output", "label": "FFmpeg output arguments", "default": [] @@ -532,7 +535,8 @@ "label": "Enabled", "default": true }, { - "type": "list-text", + "type": "list", + "object_type": "text-singleline", "key": "tags_addition", "label": "Tags addition", "default": [] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index 2fd63c7cdc..921122166b 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -4,8 +4,8 @@ "label": "Studio", "children": [ { - "key": "global", "type": "dict-invisible", + "key": "global", "label": "Global", "children": [{ "type": "schema", @@ -17,15 +17,15 @@ ] }] }, { - "key": "muster", "type": "dict-invisible", + "key": "muster", "children": [{ + "type": "dict-modifiable", + "object_type": "int", "is_group": true, "is_file": true, "key": "templates_mapping", - "label": "Muster - Templates mapping", - "type": "dict-modifiable", - "object_type": "int" + "label": "Muster - Templates mapping" }] } ] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json index 1469ffc5fc..37d525cfb6 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json @@ -7,9 +7,9 @@ "children": [ { "type": "dict-modifiable", + "object_type": "text-singleline", "key": "items", - "label": "Intent Key/Label", - "object_type": "text-singleline" + "label": "Intent Key/Label" }, { "type": "text-singleline", "key": "default", From b7a847563e0cdddad0c509a3ddb21193297e5009 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 28 Aug 2020 10:53:18 +0100 Subject: [PATCH 582/947] Revamp Build Workfile in Nuke. --- pype/hosts/nuke/lib.py | 305 ---------------------------------------- pype/hosts/nuke/menu.py | 31 ++-- 2 files changed, 21 insertions(+), 315 deletions(-) diff --git a/pype/hosts/nuke/lib.py b/pype/hosts/nuke/lib.py index 8c0e37b15d..19a0784327 100644 --- a/pype/hosts/nuke/lib.py +++ b/pype/hosts/nuke/lib.py @@ -1,7 +1,6 @@ import os import re import sys -import getpass from collections import OrderedDict from avalon import api, io, lib @@ -1060,310 +1059,6 @@ def get_write_node_template_attr(node): return avalon.nuke.lib.fix_data_for_node_create(correct_data) -class BuildWorkfile(WorkfileSettings): - """ - Building first version of workfile. - - Settings are taken from presets and db. It will add all subsets - in last version for defined representaions - - Arguments: - variable (type): description - - """ - xpos = 0 - ypos = 0 - xpos_size = 80 - ypos_size = 90 - xpos_gap = 50 - ypos_gap = 50 - pos_layer = 10 - - def __init__(self, - root_path=None, - root_node=None, - nodes=None, - to_script=None, - **kwargs): - """ - A short description. - - A bit longer description. - - Argumetns: - root_path (str): description - root_node (nuke.Node): description - nodes (list): list of nuke.Node - nodes_effects (dict): dictionary with subsets - - Example: - nodes_effects = { - "plateMain": { - "nodes": [ - [("Class", "Reformat"), - ("resize", "distort"), - ("flip", True)], - - [("Class", "Grade"), - ("blackpoint", 0.5), - ("multiply", 0.4)] - ] - }, - } - - """ - - WorkfileSettings.__init__(self, - root_node=root_node, - nodes=nodes, - **kwargs) - self.to_script = to_script - # collect data for formating - self.data_tmp = { - "project": {"name": self._project["name"], - "code": self._project["data"].get("code", "")}, - "asset": self._asset or os.environ["AVALON_ASSET"], - "task": kwargs.get("task") or api.Session["AVALON_TASK"], - "hierarchy": kwargs.get("hierarchy") or pype.get_hierarchy(), - "version": kwargs.get("version", {}).get("name", 1), - "user": getpass.getuser(), - "comment": "firstBuild", - "ext": "nk" - } - - # get presets from anatomy - anatomy = get_anatomy() - # format anatomy - anatomy_filled = anatomy.format(self.data_tmp) - - # get dir and file for workfile - self.work_dir = anatomy_filled["work"]["folder"] - self.work_file = anatomy_filled["work"]["file"] - - def save_script_as(self, path=None): - # first clear anything in open window - nuke.scriptClear() - - if not path: - dir = self.work_dir - path = os.path.join( - self.work_dir, - self.work_file).replace("\\", "/") - else: - dir = os.path.dirname(path) - - # check if folder is created - if not os.path.exists(dir): - os.makedirs(dir) - - # save script to path - nuke.scriptSaveAs(path) - - def process(self, - regex_filter=None, - version=None, - representations=["exr", "dpx", "lutJson", "mov", - "preview", "png", "jpeg", "jpg"]): - """ - A short description. - - A bit longer description. - - Args: - regex_filter (raw string): regex pattern to filter out subsets - version (int): define a particular version, None gets last - representations (list): - - Returns: - type: description - - Raises: - Exception: description - - """ - - if not self.to_script: - # save the script - self.save_script_as() - - # create viewer and reset frame range - viewer = self.get_nodes(nodes_filter=["Viewer"]) - if not viewer: - vn = nuke.createNode("Viewer") - vn["xpos"].setValue(self.xpos) - vn["ypos"].setValue(self.ypos) - else: - vn = viewer[-1] - - # move position - self.position_up() - - wn = self.write_create() - wn["xpos"].setValue(self.xpos) - wn["ypos"].setValue(self.ypos) - wn["render"].setValue(True) - vn.setInput(0, wn) - - # adding backdrop under write - self.create_backdrop(label="Render write \n\n\n\nOUTPUT", - color='0xcc1102ff', layer=-1, - nodes=[wn]) - - # move position - self.position_up(4) - - # set frame range for new viewer - self.reset_frame_range_handles() - - # get all available representations - subsets = pype.get_subsets(self._asset, - regex_filter=regex_filter, - version=version, - representations=representations) - - for name, subset in subsets.items(): - log.debug("___________________") - log.debug(name) - log.debug(subset["version"]) - - nodes_backdrop = list() - for name, subset in subsets.items(): - if "lut" in name: - continue - log.info("Building Loader to: `{}`".format(name)) - version = subset["version"] - log.info("Version to: `{}`".format(version["name"])) - representations = subset["representaions"] - for repr in representations: - rn = self.read_loader(repr) - rn["xpos"].setValue(self.xpos) - rn["ypos"].setValue(self.ypos) - wn.setInput(0, rn) - - # get editional nodes - lut_subset = [s for n, s in subsets.items() - if "lut{}".format(name.lower()) in n.lower()] - log.debug(">> lut_subset: `{}`".format(lut_subset)) - - if len(lut_subset) > 0: - lsub = lut_subset[0] - fxn = self.effect_loader(lsub["representaions"][-1]) - fxn_ypos = fxn["ypos"].value() - fxn["ypos"].setValue(fxn_ypos - 100) - nodes_backdrop.append(fxn) - - nodes_backdrop.append(rn) - # move position - self.position_right() - - # adding backdrop under all read nodes - self.create_backdrop(label="Loaded Reads", - color='0x2d7702ff', layer=-1, - nodes=nodes_backdrop) - - def read_loader(self, representation): - """ - Gets Loader plugin for image sequence or mov - - Arguments: - representation (dict): avalon db entity - - """ - context = representation["context"] - - loader_name = "LoadSequence" - if "mov" in context["representation"]: - loader_name = "LoadMov" - - loader_plugin = None - for Loader in api.discover(api.Loader): - if Loader.__name__ != loader_name: - continue - - loader_plugin = Loader - - return api.load(Loader=loader_plugin, - representation=representation["_id"]) - - def effect_loader(self, representation): - """ - Gets Loader plugin for effects - - Arguments: - representation (dict): avalon db entity - - """ - loader_name = "LoadLuts" - - loader_plugin = None - for Loader in api.discover(api.Loader): - if Loader.__name__ != loader_name: - continue - - loader_plugin = Loader - - return api.load(Loader=loader_plugin, - representation=representation["_id"]) - - def write_create(self): - """ - Create render write - - Arguments: - representation (dict): avalon db entity - - """ - task = self.data_tmp["task"] - sanitized_task = re.sub('[^0-9a-zA-Z]+', '', task) - subset_name = "render{}Main".format( - sanitized_task.capitalize()) - - Create_name = "CreateWriteRender" - - creator_plugin = None - for Creator in api.discover(api.Creator): - if Creator.__name__ != Create_name: - continue - - creator_plugin = Creator - - # return api.create() - return creator_plugin(subset_name, self._asset).process() - - def create_backdrop(self, label="", color=None, layer=0, - nodes=None): - """ - Create Backdrop node - - Arguments: - color (str): nuke compatible string with color code - layer (int): layer of node usually used (self.pos_layer - 1) - label (str): the message - nodes (list): list of nodes to be wrapped into backdrop - - """ - assert isinstance(nodes, list), "`nodes` should be a list of nodes" - layer = self.pos_layer + layer - - create_backdrop(label=label, color=color, layer=layer, nodes=nodes) - - def position_reset(self, xpos=0, ypos=0): - self.xpos = xpos - self.ypos = ypos - - def position_right(self, multiply=1): - self.xpos += (self.xpos_size * multiply) + self.xpos_gap - - def position_left(self, multiply=1): - self.xpos -= (self.xpos_size * multiply) + self.xpos_gap - - def position_down(self, multiply=1): - self.ypos -= (self.ypos_size * multiply) + self.ypos_gap - - def position_up(self, multiply=1): - self.ypos -= (self.ypos_size * multiply) + self.ypos_gap - - class ExporterReview: """ Base class object for generating review data from Nuke diff --git a/pype/hosts/nuke/menu.py b/pype/hosts/nuke/menu.py index 7306add9fe..b1ef7f47c4 100644 --- a/pype/hosts/nuke/menu.py +++ b/pype/hosts/nuke/menu.py @@ -2,10 +2,12 @@ import nuke from avalon.api import Session from pype.hosts.nuke import lib +from ...lib import BuildWorkfile from pype.api import Logger log = Logger().get_logger(__name__, "nuke") + def install(): menubar = nuke.menu("Nuke") menu = menubar.findItem(Session["AVALON_LABEL"]) @@ -20,7 +22,11 @@ def install(): log.debug("Changing Item: {}".format(rm_item)) # rm_item[1].setEnabled(False) menu.removeItem(rm_item[1].name()) - menu.addCommand(new_name, lambda: workfile_settings().reset_resolution(), index=(rm_item[0])) + menu.addCommand( + new_name, + lambda: workfile_settings().reset_resolution(), + index=(rm_item[0]) + ) # replace reset frame range from avalon core to pype's name = "Reset Frame Range" @@ -31,33 +37,38 @@ def install(): log.debug("Changing Item: {}".format(rm_item)) # rm_item[1].setEnabled(False) menu.removeItem(rm_item[1].name()) - menu.addCommand(new_name, lambda: workfile_settings().reset_frame_range_handles(), index=(rm_item[0])) + menu.addCommand( + new_name, + lambda: workfile_settings().reset_frame_range_handles(), + index=(rm_item[0]) + ) # add colorspace menu item - name = "Set colorspace" + name = "Set Colorspace" menu.addCommand( name, lambda: workfile_settings().set_colorspace(), - index=(rm_item[0]+2) + index=(rm_item[0] + 2) ) log.debug("Adding menu item: {}".format(name)) # add workfile builder menu item - name = "Build First Workfile.." + name = "Build Workfile" menu.addCommand( - name, lambda: lib.BuildWorkfile().process(), - index=(rm_item[0]+7) + name, lambda: BuildWorkfile().process(), + index=(rm_item[0] + 7) ) log.debug("Adding menu item: {}".format(name)) # add item that applies all setting above - name = "Apply all settings" + name = "Apply All Settings" menu.addCommand( - name, lambda: workfile_settings().set_context_settings(), index=(rm_item[0]+3) + name, + lambda: workfile_settings().set_context_settings(), + index=(rm_item[0] + 3) ) log.debug("Adding menu item: {}".format(name)) - def uninstall(): menubar = nuke.menu("Nuke") From 98f7be5f9047f25a87b4401ca90573549e1fd667 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:26:16 +0200 Subject: [PATCH 583/947] added log to objects --- .../tools/config_setting/config_setting/widgets/inputs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 2f0d85fd4e..573d66556e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1,4 +1,5 @@ import json +import logging from Qt import QtWidgets, QtCore, QtGui from .widgets import ( ConfigWidget, @@ -14,6 +15,13 @@ class ConfigObject: _is_overriden = False _is_modified = False _was_overriden = False + _log = None + + @property + def log(self): + if self._log is None: + self._log = logging.getLogger(self.__class__.__name__) + return self._log @property def is_modified(self): From 95a68cca5755b56cd00822a21d1e7c353ce6173e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:28:00 +0200 Subject: [PATCH 584/947] child_modified and child_overriden are abstract --- .../config_setting/config_setting/widgets/inputs.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 573d66556e..405ad3502d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -56,6 +56,19 @@ class ConfigObject: def ignore_value_changes(self, value): self._parent.ignore_value_changes = value + @property + def child_modified(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_modified`".format(self) + ) + + @property + def child_overriden(self): + """Any children item is overriden.""" + raise NotImplementedError( + "{} does not have implemented `child_overriden`".format(self) + ) def item_value(self): raise NotImplementedError( "Method `item_value` not implemented!" From 8032a38a1a80cbd52c6f8c008520e5f5cb5eb975 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:28:35 +0200 Subject: [PATCH 585/947] added few docstrings --- .../config_setting/config_setting/widgets/inputs.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 405ad3502d..eb713d5fd9 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -25,27 +25,33 @@ class ConfigObject: @property def is_modified(self): + """Has object any changes that require saving.""" return self._is_modified or (self.was_overriden != self.is_overriden) @property def is_overriden(self): + """Is object overriden so should be saved to overrides.""" return self._is_overriden or self._parent.is_overriden @property def was_overriden(self): + """Initial state after applying overrides.""" return self._was_overriden @property def is_overidable(self): + """Should care about overrides.""" return self._parent.is_overidable def any_parent_overriden(self): + """Any of parent object up to top hiearchy is overriden.""" if self._parent._is_overriden: return True return self._parent.any_parent_overriden() @property def ignore_value_changes(self): + """Most of attribute changes are ignored on value change when True.""" if not hasattr(self, "_parent"): raise NotImplementedError( "Object {} does not have `_parent` attribute".format(self) @@ -54,6 +60,7 @@ class ConfigObject: @ignore_value_changes.setter def ignore_value_changes(self, value): + """Setter for global parent item to apply changes for all inputs.""" self._parent.ignore_value_changes = value @property @@ -70,14 +77,17 @@ class ConfigObject: "{} does not have implemented `child_overriden`".format(self) ) def item_value(self): + """Value of an item without key.""" raise NotImplementedError( "Method `item_value` not implemented!" ) def config_value(self): + """Output for saving changes or overrides.""" return {self.key: self.item_value()} def value_from_values(self, values, keys=None): + """Global getter of value based on loaded values.""" if not values or values is AS_WIDGET: return NOT_SET From 1d82c562d0a0717abe3815ec16c6919edfcaf84e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:28:55 +0200 Subject: [PATCH 586/947] added is invalid attribute --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index eb713d5fd9..310717e47a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -15,6 +15,7 @@ class ConfigObject: _is_overriden = False _is_modified = False _was_overriden = False + _is_invalid = False _log = None @property @@ -38,6 +39,11 @@ class ConfigObject: """Initial state after applying overrides.""" return self._was_overriden + @property + def is_invalid(self): + """Value set in is not valid.""" + return self._is_invalid + @property def is_overidable(self): """Should care about overrides.""" From 51ad1d39372fbd2a102d1049c2520e1e313647f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:36:57 +0200 Subject: [PATCH 587/947] remove line that is not needed --- pype/tools/config_setting/config_setting/widgets/inputs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 310717e47a..b97bde5a2e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -998,7 +998,6 @@ class ListItem(QtWidgets.QWidget, ConfigObject): self.value_input.value_changed.connect(self._on_value_change) - self.value_input.item_value() self.is_single = False def _on_value_change(self, item=None): From 03c1aeac2e31df13c6232a6dc0791c95da63b2b9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:48:20 +0200 Subject: [PATCH 588/947] RawJsonInput is much simpler --- .../config_setting/widgets/inputs.py | 58 +++++-------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b97bde5a2e..aacef27f66 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -762,7 +762,6 @@ class TextMultiLineWidget(ConfigWidget, InputObject): class RawJsonInput(QtWidgets.QPlainTextEdit): - value_changed = QtCore.Signal(object) tab_length = 4 def __init__(self, *args, **kwargs): @@ -774,10 +773,6 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): ).horizontalAdvance(" ") * self.tab_length ) - self._state = None - self.is_valid = None - self.textChanged.connect(self._on_value_change) - def sizeHint(self): document = self.document() layout = document.documentLayout() @@ -788,58 +783,35 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): height += layout.blockBoundingRect(block).height() block = block.next() - value = super(RawJsonInput, self).sizeHint() - value.setHeight(height) + hint = super(RawJsonInput, self).sizeHint() + hint.setHeight(height) - return value + return hint def set_value(self, value, *, global_value=False): - self._state = None + if not value or value is NOT_SET: + value = "" if not isinstance(value, str): - value = json.dumps(value, indent=4) + try: + value = json.dumps(value, indent=4) + except Exception: + value = "" self.setPlainText(value) - def _on_value_change(self): - self.validate() - self.value_changed.emit(self) - - def validate_value(self, value): - if isinstance(value, str) and not value: - return True + def json_value(self): + return json.loads(self.toPlainText()) + def has_invalid_value(self): try: - json.loads(value) - return True - except Exception: + self.json_value() return False - - def update_style(self): - if self.is_valid is None: - return self.validate() - - if self.is_valid: - state = "" - else: - state = "invalid" - - if self._state is None or self._state != state: - self._state = state - - self.setProperty("state", state) - self.style().polish(self) + except Exception: + return True def resizeEvent(self, event): self.updateGeometry() super(RawJsonInput, self).resizeEvent(event) - def item_value(self): - return json.loads(self.toPlainText()) - - def validate(self): - value = self.toPlainText() - self.is_valid = self.validate_value(value) - self.update_style() - class RawJsonWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) From 111912cf0fd3097158e71490ae5821e7516b8609 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:49:34 +0200 Subject: [PATCH 589/947] RawJsonWidget modified to be able to handle simpler input --- .../config_setting/widgets/inputs.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index aacef27f66..0a1dd5551b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -858,12 +858,13 @@ class RawJsonWidget(ConfigWidget, InputObject): value = self.value_from_values(values) if value is not NOT_SET: self.text_input.set_value(value) + self._is_invalid = self.text_input.has_invalid_value() self.global_value = value self.start_value = self.item_value() self.override_value = NOT_SET - self.text_input.value_changed.connect(self._on_value_change) + self.text_input.textChanged.connect(self._on_value_change) def set_value(self, value, *, global_value=False): self.text_input.set_value(value) @@ -896,10 +897,11 @@ class RawJsonWidget(ConfigWidget, InputObject): if self.ignore_value_changes: return - if self.text_input.is_valid: - self._is_modified = self.item_value() != self.global_value - else: + self._is_invalid = self.text_input.has_invalid_value() + if self._is_invalid: self._is_modified = True + else: + self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True @@ -924,7 +926,9 @@ class RawJsonWidget(ConfigWidget, InputObject): widget.style().polish(widget) def item_value(self): - return self.text_input.item_value() + if self.is_invalid: + return NOT_SET + return self.text_input.json_value() class ListItem(QtWidgets.QWidget, ConfigObject): From 358362ed6c30e4780a29456bd56504d0d9a9acc8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:50:15 +0200 Subject: [PATCH 590/947] state also works with invalid --- .../config_setting/widgets/inputs.py | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0a1dd5551b..878ae8f05e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -112,12 +112,15 @@ class ConfigObject: value = value[key] return value - def style_state(self, is_overriden, is_modified): + def style_state(self, is_invalid, is_overriden, is_modified): items = [] - if is_overriden: - items.append("overriden") - if is_modified: - items.append("modified") + if is_invalid: + items.append("invalid") + else: + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") return "-".join(items) or self.default_state def add_children_gui(self, child_configuration, values): @@ -306,7 +309,9 @@ class BooleanWidget(ConfigWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -413,7 +418,9 @@ class IntegerWidget(ConfigWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -529,7 +536,9 @@ class FloatWidget(ConfigWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -637,7 +646,9 @@ class TextSingleLineWidget(ConfigWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -743,7 +754,9 @@ class TextMultiLineWidget(ConfigWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -911,7 +924,9 @@ class RawJsonWidget(ConfigWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -1211,7 +1226,9 @@ class ListWidget(ConfigWidget, InputObject): self.update_style() def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -1521,7 +1538,9 @@ class ModifiableDict(ExpandingWidget, InputObject): self.update_style() def update_style(self): - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -1654,7 +1673,9 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): def update_style(self, is_overriden=None): child_modified = self.child_modified - child_state = self.style_state(self.child_overriden, child_modified) + child_state = self.style_state( + self.child_invalid, self.child_overriden, child_modified + ) if child_state: child_state = "child-{}".format(child_state) @@ -1663,7 +1684,9 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): self.style().polish(self) self._child_state = child_state - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return @@ -1856,7 +1879,9 @@ class DictWidget(ConfigWidget, ConfigObject): def update_style(self, is_overriden=None): child_modified = self.child_modified - child_state = self.style_state(self.child_overriden, child_modified) + child_state = self.style_state( + self.child_invalid, self.child_overriden, child_modified + ) if child_state: child_state = "child-{}".format(child_state) @@ -1865,7 +1890,9 @@ class DictWidget(ConfigWidget, ConfigObject): self.style().polish(self) self._child_state = child_state - state = self.style_state(self.is_overriden, self.is_modified) + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) if self._state == state: return From 55175426c48fce8cfecaf00f299eb80579360f0a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 12:50:38 +0200 Subject: [PATCH 591/947] implemented child invalid to be able show them --- .../config_setting/widgets/inputs.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 878ae8f05e..9c515ece8c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -82,6 +82,14 @@ class ConfigObject: raise NotImplementedError( "{} does not have implemented `child_overriden`".format(self) ) + + @property + def child_invalid(self): + """Any children item does not have valid value.""" + raise NotImplementedError( + "{} does not have implemented `child_invalid`".format(self) + ) + def item_value(self): """Value of an item without key.""" raise NotImplementedError( @@ -201,6 +209,10 @@ class InputObject(ConfigObject): def child_overriden(self): return self._is_overriden + @property + def child_invalid(self): + return self.is_invalid + def reset_children_attributes(self): return @@ -1715,6 +1727,13 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): return True return False + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + def item_value(self): output = {} for input_field in self.input_fields: @@ -1921,6 +1940,13 @@ class DictWidget(ConfigWidget, ConfigObject): return True return False + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + def item_value(self): output = {} for input_field in self.input_fields: @@ -2011,6 +2037,13 @@ class DictInvisible(ConfigWidget, ConfigObject): return True return False + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + def item_value(self): output = {} for input_field in self.input_fields: From 88dd72f76182dddc2d30b513910c5c0b5a383713 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 13:03:20 +0200 Subject: [PATCH 592/947] added hover color to buttons and separated item-tool btn --- pype/tools/config_setting/config_setting/style/style.css | 8 ++++++-- .../tools/config_setting/config_setting/widgets/inputs.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index ce014f0768..01e6503e6e 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -65,14 +65,18 @@ QPushButton { border-radius: 3px; padding: 5px; } -QPushButton[btn-type="text-list"] { +QPushButton:hover { + background-color: #31424e; +} +QPushButton[btn-type="tool-item"] { border: 1px solid #bfccd6; border-radius: 10px; } -QPushButton[btn-type="text-list"]:hover { +QPushButton[btn-type="tool-item"]:hover { border-color: #137cbd; color: #137cbd; + background-color: transparent; } QPushButton[btn-type="expand-toggle"] { diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9c515ece8c..aada384286 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -989,8 +989,8 @@ class ListItem(QtWidgets.QWidget, ConfigObject): self.add_btn.setFixedSize(self._btn_size, self._btn_size) self.remove_btn.setFixedSize(self._btn_size, self._btn_size) - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") + self.add_btn.setProperty("btn-type", "tool-item") + self.remove_btn.setProperty("btn-type", "tool-item") layout.addWidget(self.value_input, 1) layout.addWidget(self.add_btn, 0) @@ -1282,8 +1282,8 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) - self.add_btn.setProperty("btn-type", "text-list") - self.remove_btn.setProperty("btn-type", "text-list") + self.add_btn.setProperty("btn-type", "tool-item") + self.remove_btn.setProperty("btn-type", "tool-item") layout.addWidget(self.key_input, 0) layout.addWidget(self.value_input, 1) From dd7772d36881c0f9c7b9454cb786180198e64ce6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 13:03:35 +0200 Subject: [PATCH 593/947] fixed dict widget invalid style --- pype/tools/config_setting/config_setting/widgets/inputs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index aada384286..afe805c69f 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1898,8 +1898,9 @@ class DictWidget(ConfigWidget, ConfigObject): def update_style(self, is_overriden=None): child_modified = self.child_modified + child_invalid = self.child_invalid child_state = self.style_state( - self.child_invalid, self.child_overriden, child_modified + child_invalid, self.child_overriden, child_modified ) if child_state: child_state = "child-{}".format(child_state) @@ -1910,7 +1911,7 @@ class DictWidget(ConfigWidget, ConfigObject): self._child_state = child_state state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + child_invalid, self.is_overriden, self.is_modified ) if self._state == state: return From a05e45acebd2894ffdf0544ba4f84a2acaedbd2c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 13:03:50 +0200 Subject: [PATCH 594/947] added invalid colors to style --- .../config_setting/style/style.css | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 01e6503e6e..c575ed64c3 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -47,6 +47,8 @@ QLabel[state="overriden-modified"] {color: #137cbd;} QLabel[state="overriden-modified"]:hover {color: #1798e8;} QLabel[state="overriden"] {color: #ff8c1a;} QLabel[state="overriden"]:hover {color: #ffa64d;} +QLabel[state="invalid"] {color: #ad2e2e; font-weight: bold;} +QLabel[state="invalid"]:hover {color: #ad2e2e; font-weight: bold;} QWidget[input-state="modified"] { border-color: #137cbd; @@ -60,6 +62,10 @@ QWidget[input-state="overriden"] { border-color: #ff8c1a; } +QWidget[input-state="invalid"] { + border-color: #ad2e2e; +} + QPushButton { border: 1px solid #aaaaaa; border-radius: 3px; @@ -96,20 +102,18 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } -#RawJsonInput[state="invalid"] { - border-color: #ab2e46; - border-width: 2px; -} - #DictKey[state="modified"] { - border-left-color: #137cbd; + border-color: #137cbd; } #DictKey[state="overriden"] { - border-left-color: #00f; + border-color: #00f; } #DictKey[state="overriden-modified"] { - border-left-color: #0f0; + border-color: #0f0; +} +#DictKey[state="invalid"] { + border-color: #ad2e2e; } #DictLabel { @@ -136,6 +140,13 @@ QPushButton[btn-type="expand-toggle"] { border-color: #137cbd; } +#ExpandingWidget[state="child-invalid"], #ModifiableDict[state="child-invalid"], #DictWidget[state="child-invalid"] { + border-color: #ad2e2e; +} +#ExpandingWidget[state="child-invalid"]:hover, #ModifiableDict[state="child-invalid"]:hover, #DictWidget[state="child-invalid"]:hover { + border-color: #c93636; +} + #ExpandingWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"] { border-color: #e67300; } From 76777790523f77aae9a235d8567a010ce4c9f6f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:06:41 +0200 Subject: [PATCH 595/947] added methods for getting invalid items --- .../config_setting/config_setting/widgets/inputs.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index afe805c69f..be3a1813d3 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -90,6 +90,12 @@ class ConfigObject: "{} does not have implemented `child_invalid`".format(self) ) + def get_invalid(self): + """Returns invalid items all down the hierarchy.""" + raise NotImplementedError( + "{} does not have implemented `get_invalid`".format(self) + ) + def item_value(self): """Value of an item without key.""" raise NotImplementedError( @@ -213,6 +219,12 @@ class InputObject(ConfigObject): def child_invalid(self): return self.is_invalid + def get_invalid(self): + output = [] + if self.is_invalid: + output.append(self) + return output + def reset_children_attributes(self): return From c8e2963c91b0e467d00ac02ea264442066afe91c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:31:42 +0200 Subject: [PATCH 596/947] basees won't allow save invalid values --- .../config_setting/widgets/base.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index d5f9a05aea..34405d2ad0 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -99,6 +99,30 @@ class StudioWidget(QtWidgets.QWidget): self.schema = schema def _save(self): + has_invalid = False + for item in self.input_fields: + if item.child_invalid: + has_invalid = True + + if has_invalid: + invalid_items = [] + for item in self.input_fields: + invalid_items.extend(item.get_invalid()) + msg_box = QtWidgets.QMessageBox( + QtWidgets.QMessageBox.Warning, + "Invalid input", + "There is invalid value in one of inputs." + " Please lead red color and fix them." + ) + msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) + msg_box.exec_() + + first_invalid_item = invalid_items[0] + self.scroll_widget.ensureWidgetVisible(first_invalid_item) + if first_invalid_item.isVisible(): + first_invalid_item.setFocus(True) + return + all_values = {} for item in self.input_fields: all_values.update(item.config_value()) @@ -401,6 +425,30 @@ class ProjectWidget(QtWidgets.QWidget): self.ignore_value_changes = False def _save(self): + has_invalid = False + for item in self.input_fields: + if item.child_invalid: + has_invalid = True + + if has_invalid: + invalid_items = [] + for item in self.input_fields: + invalid_items.extend(item.get_invalid()) + msg_box = QtWidgets.QMessageBox( + QtWidgets.QMessageBox.Warning, + "Invalid input", + "There is invalid value in one of inputs." + " Please lead red color and fix them." + ) + msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) + msg_box.exec_() + + first_invalid_item = invalid_items[0] + self.scroll_widget.ensureWidgetVisible(first_invalid_item) + if first_invalid_item.isVisible(): + first_invalid_item.setFocus(True) + return + if self.project_name is None: self._save_defaults() else: From 31227069ffe219f3fad787d974772c1711eb6995 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:45:36 +0200 Subject: [PATCH 597/947] update styles after initialization --- .../config_setting/widgets/base.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 34405d2ad0..283a5ad0a4 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -93,10 +93,10 @@ class StudioWidget(QtWidgets.QWidget): self.input_fields.clear() values = {"studio": config.studio_configurations()} - schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") - self.keys = schema.get("keys", []) - self.add_children_gui(schema, values) - self.schema = schema + self.schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") + self.keys = self.schema.get("keys", []) + self.add_children_gui(self.schema, values) + self.hierarchical_style_update() def _save(self): has_invalid = False @@ -395,10 +395,10 @@ class ProjectWidget(QtWidgets.QWidget): def reset(self): values = {"project": config.global_project_configurations()} - schema = lib.gui_schema("projects_schema", "0_project_gui_schema") - self.keys = schema.get("keys", []) - self.add_children_gui(schema, values) - self.schema = schema + self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") + self.keys = self.schema.get("keys", []) + self.add_children_gui(self.schema, values) + self.hierarchical_style_update() def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] From de1ed6d639a2dfbbf86343615f80afc302d9d89e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:48:26 +0200 Subject: [PATCH 598/947] added default values to inputs --- .../config_setting/widgets/inputs.py | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index be3a1813d3..7d25880045 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -239,6 +239,7 @@ class BooleanWidget(ConfigWidget, InputObject): self._as_widget = values is AS_WIDGET self.is_group = input_data.get("is_group", False) + self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -268,14 +269,12 @@ class BooleanWidget(ConfigWidget, InputObject): keys = list(parent_keys) keys.append(self.key) self.keys = keys - - default_value = input_data.get("default", NOT_SET) value = self.value_from_values(values) if value is not NOT_SET: self.checkbox.setChecked(value) - elif default_value is not NOT_SET: - self.checkbox.setChecked(default_value) + elif self.default_value is not NOT_SET: + self.checkbox.setChecked(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -362,6 +361,7 @@ class IntegerWidget(ConfigWidget, InputObject): self._as_widget = values is AS_WIDGET self.is_group = input_data.get("is_group", False) + self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -394,6 +394,9 @@ class IntegerWidget(ConfigWidget, InputObject): if value is not NOT_SET: self.int_input.setValue(value) + elif self.default_value is not NOT_SET: + self.int_input.setValue(self.default_value) + self.global_value = value self.start_value = self.item_value() self.override_value = NOT_SET @@ -472,6 +475,7 @@ class FloatWidget(ConfigWidget, InputObject): self._as_widget = values is AS_WIDGET self.is_group = input_data.get("is_group", False) + self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -514,6 +518,9 @@ class FloatWidget(ConfigWidget, InputObject): if value is not NOT_SET: self.float_input.setValue(value) + elif self.default_value is not NOT_SET: + self.float_input.setValue(self.default_value) + self.start_value = self.item_value() self.global_value = value self.override_value = NOT_SET @@ -590,6 +597,7 @@ class TextSingleLineWidget(ConfigWidget, InputObject): self._as_widget = values is AS_WIDGET self.is_group = input_data.get("is_group", False) + self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -622,6 +630,9 @@ class TextSingleLineWidget(ConfigWidget, InputObject): if value is not NOT_SET: self.text_input.setText(value) + elif self.default_value is not NOT_SET: + self.text_input.setText(self.default_value) + self.global_value = value self.start_value = self.item_value() self.override_value = NOT_SET @@ -700,6 +711,7 @@ class TextMultiLineWidget(ConfigWidget, InputObject): self._as_widget = values is AS_WIDGET self.is_group = input_data.get("is_group", False) + self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -732,6 +744,9 @@ class TextMultiLineWidget(ConfigWidget, InputObject): if value is not NOT_SET: self.text_input.setPlainText(value) + elif self.default_value is not NOT_SET: + self.text_input.setPlainText(self.default_value) + self.global_value = value self.start_value = self.item_value() self.override_value = NOT_SET @@ -860,6 +875,7 @@ class RawJsonWidget(ConfigWidget, InputObject): self._as_widget = values is AS_WIDGET self.is_group = input_data.get("is_group", False) + self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -895,6 +911,9 @@ class RawJsonWidget(ConfigWidget, InputObject): value = self.value_from_values(values) if value is not NOT_SET: self.text_input.set_value(value) + + elif self.default_value is not NOT_SET: + self.text_input.set_value(self.default_value) self._is_invalid = self.text_input.has_invalid_value() self.global_value = value @@ -1050,6 +1069,7 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.object_type = input_data["object_type"] + self.default_value = input_data.get("default", NOT_SET) self.key = input_data["key"] keys = list(parent_keys) @@ -1061,6 +1081,10 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): for item_value in value: self.add_row(value=item_value) + elif self.default_value is not NOT_SET: + for item_value in self.default_value: + self.add_row(value=item_value) + if self.count() == 0: self.add_row() @@ -1392,6 +1416,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.object_type = input_data["object_type"] + self.default_value = input_data.get("default", NOT_SET) self.key = input_data["key"] keys = list(parent_keys) @@ -1403,6 +1428,10 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): for item_key, item_value in value.items(): self.add_row(key=item_key, value=item_value) + elif self.default_value is not NOT_SET: + for item_key, item_value in self.default_value.items(): + self.add_row(key=item_key, value=item_value) + if self.count() == 0: self.add_row() From f6839a79530b490fc9fddb576573576f7b64732a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:48:54 +0200 Subject: [PATCH 599/947] implemented get_invalid for dictionaries --- .../config_setting/widgets/inputs.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 7d25880045..e2aa2e01a1 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1775,6 +1775,12 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): return True return False + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + def item_value(self): output = {} for input_field in self.input_fields: @@ -1989,6 +1995,12 @@ class DictWidget(ConfigWidget, ConfigObject): return True return False + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + def item_value(self): output = {} for input_field in self.input_fields: @@ -2086,6 +2098,12 @@ class DictInvisible(ConfigWidget, ConfigObject): return True return False + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + def item_value(self): output = {} for input_field in self.input_fields: @@ -2249,6 +2267,19 @@ class DictFormWidget(ConfigWidget, ConfigObject): return True return False + @property + def child_invalid(self): + for input_field in self.input_fields.values(): + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields.values(): + output.extend(input_field.get_invalid()) + return output + def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] key = child_configuration["key"] From 2dc39e276f1961a9c58aff1d3d3c03791d73c6dd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:49:43 +0200 Subject: [PATCH 600/947] set is modified on input init --- .../config_setting/widgets/inputs.py | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e2aa2e01a1..20ccf20290 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -276,9 +276,11 @@ class BooleanWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.checkbox.setChecked(self.default_value) + self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value self.checkbox.stateChanged.connect(self._on_value_change) @@ -397,9 +399,11 @@ class IntegerWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.int_input.setValue(self.default_value) + self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value self.int_input.valueChanged.connect(self._on_value_change) @@ -521,9 +525,11 @@ class FloatWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.float_input.setValue(self.default_value) + self.override_value = NOT_SET self.start_value = self.item_value() self.global_value = value - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value self.float_input.valueChanged.connect(self._on_value_change) @@ -633,9 +639,11 @@ class TextSingleLineWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.text_input.setText(self.default_value) + self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value self.text_input.textChanged.connect(self._on_value_change) @@ -747,9 +755,11 @@ class TextMultiLineWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.text_input.setPlainText(self.default_value) + self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value self.text_input.textChanged.connect(self._on_value_change) @@ -916,9 +926,11 @@ class RawJsonWidget(ConfigWidget, InputObject): self.text_input.set_value(self.default_value) self._is_invalid = self.text_input.has_invalid_value() + self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value self.text_input.textChanged.connect(self._on_value_change) @@ -1088,9 +1100,11 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): if self.count() == 0: self.add_row() + self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value def set_value(self, value, *, global_value=False): previous_inputs = tuple(self.input_fields) @@ -1435,9 +1449,12 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): if self.count() == 0: self.add_row() + self.override_value = NOT_SET + self.global_value = value self.start_value = self.config_value() - self.override_value = NOT_SET + + self._is_modified = self.global_value != self.start_value @property def is_group(self): @@ -1555,8 +1572,11 @@ class ModifiableDict(ExpandingWidget, InputObject): self.key = input_data["key"] - self.global_value = self.item_value() self.override_value = NOT_SET + self.start_value = self.value_widget.start_value + self.global_value = self.value_widget.global_value + + self._is_modified = self.global_value != self.start_value def _on_value_change(self, item=None): if self.ignore_value_changes: From 61e11552667839bbe9b53f7ddcf8f059bd0fa6dd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:50:05 +0200 Subject: [PATCH 601/947] rawjson set's invalid bool even if values changes are ignored --- pype/tools/config_setting/config_setting/widgets/inputs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 20ccf20290..9fcbd20454 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -962,6 +962,7 @@ class RawJsonWidget(ConfigWidget, InputObject): self.update_style() def _on_value_change(self, item=None): + self._is_invalid = self.text_input.has_invalid_value() if self.ignore_value_changes: return @@ -1064,7 +1065,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def config_value(self): return self.value_input.item_value() - +# TODO Move subwidget to main widget class ListSubWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) From ad69ee3e4b9179da221b39de05c2248a39874296 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 16:59:55 +0200 Subject: [PATCH 602/947] allow empty objects in raw json --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9fcbd20454..b290927ed1 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -851,9 +851,9 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): return hint def set_value(self, value, *, global_value=False): - if not value or value is NOT_SET: + if value is NOT_SET: value = "" - if not isinstance(value, str): + elif not isinstance(value, str): try: value = json.dumps(value, indent=4) except Exception: From 24ab3fcaad29b380fa854b2ac524f085bbd3b15a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 17:00:17 +0200 Subject: [PATCH 603/947] added few default values --- .../projects_schema/1_plugins_gui_schema.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 9f40c7871b..dbbcaf2b36 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -81,7 +81,8 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled" + "label": "Enabled", + "default": true }, { "type": "text-singleline", "key": "note_with_intent_template", @@ -157,11 +158,13 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled" + "label": "Enabled", + "default": true }, { "type": "raw-json", "key": "profiles", - "label": "Profiles" + "label": "Profiles", + "default": [] } ] }, { From 81a433941f1b7adbb4281157d7d1f8f76566f6e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 17:04:11 +0200 Subject: [PATCH 604/947] modified default min/max for number inputs --- pype/tools/config_setting/config_setting/widgets/widgets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 89f6782cfd..c2de371ffc 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -5,6 +5,8 @@ class ModifiedIntSpinBox(QtWidgets.QSpinBox): def __init__(self, *args, **kwargs): super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setMinimum(-99999) + self.setMaximum(99999) def wheelEvent(self, event): if self.hasFocus(): @@ -17,6 +19,8 @@ class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): def __init__(self, *args, **kwargs): super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setMinimum(-99999) + self.setMaximum(99999) def wheelEvent(self, event): if self.hasFocus(): From a910969c02b1e3488e4432b9c02fe62d3018df39 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 17:08:44 +0200 Subject: [PATCH 605/947] default number are settable by kwargs --- .../config_setting/widgets/widgets.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index c2de371ffc..0b493d307d 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -3,10 +3,12 @@ from Qt import QtWidgets, QtCore, QtGui class ModifiedIntSpinBox(QtWidgets.QSpinBox): def __init__(self, *args, **kwargs): + min_value = kwargs.pop("min", -99999) + max_value = kwargs.pop("max", 99999) super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setMinimum(-99999) - self.setMaximum(99999) + self.setMinimum(min_value) + self.setMaximum(max_value) def wheelEvent(self, event): if self.hasFocus(): @@ -17,10 +19,14 @@ class ModifiedIntSpinBox(QtWidgets.QSpinBox): class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): def __init__(self, *args, **kwargs): + min_value = kwargs.pop("min", -99999) + max_value = kwargs.pop("max", 99999) + decimals = kwargs.pop("decimal", 2) super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setMinimum(-99999) - self.setMaximum(99999) + self.setDecimals(decimals) + self.setMinimum(min_value) + self.setMaximum(max_value) def wheelEvent(self, event): if self.hasFocus(): From 0fb411cff071d383d0219a83bdf75e99ccb8f9e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 17:15:10 +0200 Subject: [PATCH 606/947] added modifiers for number inputs --- .../studio_schema/1_tray_items.json | 4 +++- .../config_setting/widgets/inputs.py | 16 ++++++++++++++-- .../config_setting/widgets/widgets.py | 8 ++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index e6f9a41e51..45b1bc65ce 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -69,7 +69,9 @@ { "type": "int", "key": "default_port", - "label": "Default Port" + "label": "Default Port", + "minimum": 1, + "maximum": 65535 } ] }, { diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b290927ed1..4136069d6a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -355,6 +355,7 @@ class BooleanWidget(ConfigWidget, InputObject): class IntegerWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) + input_modifiers = ("minimum", "maximum") def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -373,7 +374,12 @@ class IntegerWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.int_input = ModifiedIntSpinBox(self) + kwargs = { + modifier: input_data.get(modifier) + for modifier in self.input_modifiers + if input_data.get(modifier) + } + self.int_input = ModifiedIntSpinBox(self, **kwargs) self.setFocusProxy(self.int_input) @@ -471,6 +477,7 @@ class IntegerWidget(ConfigWidget, InputObject): class FloatWidget(ConfigWidget, InputObject): value_changed = QtCore.Signal(object) + input_modifiers = ("minimum", "maximum", "decimal") def __init__( self, input_data, values, parent_keys, parent, label_widget=None @@ -489,7 +496,12 @@ class FloatWidget(ConfigWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.float_input = ModifiedFloatSpinBox(self) + kwargs = { + modifier: input_data.get(modifier) + for modifier in self.input_modifiers + if input_data.get(modifier) + } + self.float_input = ModifiedFloatSpinBox(self, **kwargs) self.setFocusProxy(self.float_input) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 0b493d307d..b824ea8720 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -3,8 +3,8 @@ from Qt import QtWidgets, QtCore, QtGui class ModifiedIntSpinBox(QtWidgets.QSpinBox): def __init__(self, *args, **kwargs): - min_value = kwargs.pop("min", -99999) - max_value = kwargs.pop("max", 99999) + min_value = kwargs.pop("minimum", -99999) + max_value = kwargs.pop("maximum", 99999) super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setMinimum(min_value) @@ -19,8 +19,8 @@ class ModifiedIntSpinBox(QtWidgets.QSpinBox): class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): def __init__(self, *args, **kwargs): - min_value = kwargs.pop("min", -99999) - max_value = kwargs.pop("max", 99999) + min_value = kwargs.pop("minimum", -99999) + max_value = kwargs.pop("maximum", 99999) decimals = kwargs.pop("decimal", 2) super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) From 95aa493aaf5f35cf8db77ccd2f0190a081d2f6a9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 17:21:51 +0200 Subject: [PATCH 607/947] added possibility of input modifier for list and modifiable dict --- .../studio_schema/0_studio_gui_schema.json | 4 ++++ .../config_setting/widgets/inputs.py | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index 921122166b..db465fb392 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -22,6 +22,10 @@ "children": [{ "type": "dict-modifiable", "object_type": "int", + "input_modifiers": { + "minimum": 0, + "maximum": 300 + }, "is_group": true, "is_file": true, "key": "templates_mapping", diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4136069d6a..35b8719bb6 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -11,6 +11,8 @@ from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass class ConfigObject: + input_modifiers = tuple() + default_state = "" _is_overriden = False _is_modified = False @@ -1018,7 +1020,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, object_type, parent): + def __init__(self, object_type, input_modifiers, parent): self._parent = parent super(ListItem, self).__init__(parent) @@ -1029,7 +1031,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): ItemKlass = TypeToKlass.types[object_type] self.value_input = ItemKlass( - {}, + input_modifiers, AS_WIDGET, [], self, @@ -1077,6 +1079,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def config_value(self): return self.value_input.item_value() + # TODO Move subwidget to main widget class ListSubWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) @@ -1095,6 +1098,7 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) + self.input_modifiers = input_data.get("input_modifiers") or {} self.key = input_data["key"] keys = list(parent_keys) @@ -1157,7 +1161,7 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): def add_row(self, row=None, value=None): # Create new item - item_widget = ListItem(self.object_type, self) + item_widget = ListItem(self.object_type, self.input_modifiers, self) # Set/unset if new item is single item current_count = self.count() @@ -1318,7 +1322,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, object_type, parent): + def __init__(self, object_type, input_modifiers, parent): self._parent = parent super(ModifiableDictItem, self).__init__(parent) @@ -1333,7 +1337,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.key_input.setObjectName("DictKey") self.value_input = ItemKlass( - {}, + input_modifiers, AS_WIDGET, [], self, @@ -1444,6 +1448,7 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) + self.input_modifiers = input_data.get("input_modifiers") or {} self.key = input_data["key"] keys = list(parent_keys) @@ -1485,7 +1490,9 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): def add_row(self, row=None, key=None, value=None): # Create new item - item_widget = ModifiableDictItem(self.object_type, self) + item_widget = ModifiableDictItem( + self.object_type, self.input_modifiers, self + ) # Set/unset if new item is single item current_count = self.count() From 7a382f3f2073fdbe34982b50781ad22f3054bc8c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Aug 2020 17:22:51 +0200 Subject: [PATCH 608/947] removed global input_modifiers = tuple() --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 35b8719bb6..e5a63e635a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -11,8 +11,6 @@ from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass class ConfigObject: - input_modifiers = tuple() - default_state = "" _is_overriden = False _is_modified = False From f76331207cec10b3476b8f9dcdb5024d82e25f48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 10:02:06 +0200 Subject: [PATCH 609/947] fixed json overrides on project change --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e5a63e635a..244d3642ae 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -965,9 +965,11 @@ class RawJsonWidget(ConfigWidget, InputObject): self.override_value = override_value if override_value is NOT_SET: self._is_overriden = False + self._was_overriden = False value = self.start_value else: self._is_overriden = True + self._was_overriden = True value = override_value self.set_value(value) @@ -981,6 +983,8 @@ class RawJsonWidget(ConfigWidget, InputObject): self._is_invalid = self.text_input.has_invalid_value() if self._is_invalid: self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value else: self._is_modified = self.item_value() != self.global_value From 0797edccfd0e65e1e0d5712d853c99c2cf5a1f8b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 10:02:32 +0200 Subject: [PATCH 610/947] overrides are loaded with metadatakey --- pype/tools/config_setting/config_setting/widgets/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 283a5ad0a4..fd558e1ea3 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -412,12 +412,13 @@ class ProjectWidget(QtWidgets.QWidget): def _on_project_change(self): project_name = self.project_list_widget.project_name() if project_name is None: - overrides = lib.NOT_SET + _overrides = lib.NOT_SET self.is_overidable = False else: - overrides = config.project_configurations_overrides(project_name) + _overrides = config.project_configurations_overrides(project_name) self.is_overidable = True + overrides = lib.convert_overrides_to_gui_data(_overrides) self.project_name = project_name self.ignore_value_changes = True for item in self.input_fields: From de0e9f2effe964ab54bdfc53e4634178ca3f7db1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 10:02:51 +0200 Subject: [PATCH 611/947] project overrides cancel modifcation changes on save --- pype/tools/config_setting/config_setting/widgets/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index fd558e1ea3..b412db5308 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -454,6 +454,7 @@ class ProjectWidget(QtWidgets.QWidget): self._save_defaults() else: self._save_overrides() + self._on_project_change() def _save_overrides(self): _data = {} From 6ea8b1ef051831f211c1e90a1aa06f34f150dba4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 10:03:00 +0200 Subject: [PATCH 612/947] changed default for few plugins --- .../projects_schema/1_plugins_gui_schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index dbbcaf2b36..c70daab32c 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -82,7 +82,7 @@ "type": "boolean", "key": "enabled", "label": "Enabled", - "default": true + "default": false }, { "type": "text-singleline", "key": "note_with_intent_template", @@ -397,7 +397,7 @@ "type": "boolean", "key": "enabled", "label": "Enabled", - "default": false + "default": true }, { "type": "raw-json", "key": "nodes", From 78a336939e1b45a32ff8623de57bcec11d8b1b2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 10:11:09 +0200 Subject: [PATCH 613/947] added possibility to have expanded expandable dict by default --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 244d3642ae..b2b3b3990b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1690,6 +1690,10 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): self.set_content_widget(content_widget) + expanded = input_data.get("expanded", False) + if expanded: + self.toggle_content() + self.setAttribute(QtCore.Qt.WA_StyledBackground) self.content_widget = content_widget From 1bbfae31f9f7b2b25b46b7359fa7a4fe6f7ab028 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 11:05:57 +0200 Subject: [PATCH 614/947] added update_global_values method --- .../config_setting/widgets/inputs.py | 124 ++++++++++++++---- 1 file changed, 97 insertions(+), 27 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b2b3b3990b..950fdb5bf8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -262,13 +262,22 @@ class BooleanWidget(ConfigWidget, InputObject): layout.addWidget(self.checkbox, 1) - value = NOT_SET if not self._as_widget: self.label_widget = label_widget + self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys + + self.update_global_values(values) + self.override_value = NOT_SET + + self.checkbox.stateChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: self.checkbox.setChecked(value) @@ -276,14 +285,11 @@ class BooleanWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.checkbox.setChecked(self.default_value) - self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - self.checkbox.stateChanged.connect(self._on_value_change) - def set_value(self, value, *, global_value=False): # Ignore value change because if `self.isChecked()` has same # value as `value` the `_on_value_change` is not triggered @@ -389,7 +395,6 @@ class IntegerWidget(ConfigWidget, InputObject): layout.addWidget(label_widget, 0) layout.addWidget(self.int_input, 1) - value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -398,6 +403,15 @@ class IntegerWidget(ConfigWidget, InputObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + self.override_value = NOT_SET + + self.int_input.valueChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: self.int_input.setValue(value) @@ -405,14 +419,11 @@ class IntegerWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.int_input.setValue(self.default_value) - self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - self.int_input.valueChanged.connect(self._on_value_change) - def set_value(self, value, *, global_value=False): self.int_input.setValue(value) if global_value: @@ -521,7 +532,6 @@ class FloatWidget(ConfigWidget, InputObject): layout.addWidget(label_widget, 0) layout.addWidget(self.float_input, 1) - value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -530,6 +540,15 @@ class FloatWidget(ConfigWidget, InputObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + self.override_value = NOT_SET + + self.float_input.valueChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: self.float_input.setValue(value) @@ -537,14 +556,11 @@ class FloatWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.float_input.setValue(self.default_value) - self.override_value = NOT_SET - self.start_value = self.item_value() self.global_value = value + self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - self.float_input.valueChanged.connect(self._on_value_change) - def set_value(self, value, *, global_value=False): self.float_input.setValue(value) if global_value: @@ -635,7 +651,6 @@ class TextSingleLineWidget(ConfigWidget, InputObject): layout.addWidget(label_widget, 0) layout.addWidget(self.text_input, 1) - value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -644,6 +659,15 @@ class TextSingleLineWidget(ConfigWidget, InputObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + self.override_value = NOT_SET + + self.text_input.textChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: self.text_input.setText(value) @@ -651,14 +675,11 @@ class TextSingleLineWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.text_input.setText(self.default_value) - self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - self.text_input.textChanged.connect(self._on_value_change) - def set_value(self, value, *, global_value=False): self.text_input.setText(value) if global_value: @@ -751,7 +772,6 @@ class TextMultiLineWidget(ConfigWidget, InputObject): layout.addWidget(label_widget, 0) layout.addWidget(self.text_input, 1) - value = NOT_SET if not self._as_widget: self.label_widget = label_widget @@ -760,6 +780,15 @@ class TextMultiLineWidget(ConfigWidget, InputObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + self.override_value = NOT_SET + + self.text_input.textChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: self.text_input.setPlainText(value) @@ -767,14 +796,11 @@ class TextMultiLineWidget(ConfigWidget, InputObject): elif self.default_value is not NOT_SET: self.text_input.setPlainText(self.default_value) - self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - self.text_input.textChanged.connect(self._on_value_change) - def set_value(self, value, *, global_value=False): self.text_input.setPlainText(value) if global_value: @@ -921,7 +947,8 @@ class RawJsonWidget(ConfigWidget, InputObject): layout.addWidget(label_widget, 0) layout.addWidget(self.text_input, 1) - value = NOT_SET + self.override_value = NOT_SET + if not self._as_widget: self.label_widget = label_widget @@ -930,22 +957,27 @@ class RawJsonWidget(ConfigWidget, InputObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + self.text_input.textChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: self.text_input.set_value(value) elif self.default_value is not NOT_SET: self.text_input.set_value(self.default_value) + self._is_invalid = self.text_input.has_invalid_value() - self.override_value = NOT_SET self.global_value = value self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - self.text_input.textChanged.connect(self._on_value_change) - def set_value(self, value, *, global_value=False): self.text_input.set_value(value) if global_value: @@ -1107,6 +1139,13 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + self.override_value = NOT_SET + + def update_global_values(self, values): + old_inputs = tuple(self.input_fields) + value = self.value_from_values(values) if value is not NOT_SET: for item_value in value: @@ -1119,7 +1158,9 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): if self.count() == 0: self.add_row() - self.override_value = NOT_SET + for old_input in old_inputs: + self.remove_row(old_input) + self.global_value = value self.start_value = self.item_value() @@ -1266,6 +1307,9 @@ class ListWidget(ConfigWidget, InputObject): self.value_widget.value_changed.connect(self._on_value_change) + def update_global_values(self, values): + self.value_widget.update_global_values(values) + @property def start_value(self): return self.value_widget.start_value @@ -1433,6 +1477,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): return {key: value} +# TODO Move subwidget to main widget class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) @@ -1457,6 +1502,11 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): keys.append(self.key) self.keys = keys + self.update_global_values(values) + + def update_global_values(self, values): + old_inputs = tuple(self.input_fields) + value = self.value_from_values(values) if value is not NOT_SET: for item_key, item_value in value.items(): @@ -1469,7 +1519,8 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): if self.count() == 0: self.add_row() - self.override_value = NOT_SET + for old_input in old_inputs: + self.remove_row(old_input) self.global_value = value self.start_value = self.config_value() @@ -1600,6 +1651,9 @@ class ModifiableDict(ExpandingWidget, InputObject): self._is_modified = self.global_value != self.start_value + def update_global_values(self, values): + self.value_widget.update_global_values(values) + def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1723,6 +1777,10 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def update_global_values(self, values): + for item in self.input_fields: + item.update_global_values(values) + def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -1943,6 +2001,10 @@ class DictWidget(ConfigWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def update_global_values(self, values): + for item in self.input_fields: + item.update_global_values(values) + def apply_overrides(self, override_value): # Make sure this is set to False self._is_overriden = False @@ -2207,6 +2269,10 @@ class DictInvisible(ConfigWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def update_global_values(self, values): + for item in self.input_fields: + item.update_global_values(values) + def apply_overrides(self, override_value): self._is_overriden = False for item in self.input_fields: @@ -2294,6 +2360,10 @@ class DictFormWidget(ConfigWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def update_global_values(self, values): + for item in self.input_fields.values(): + item.update_global_values(values) + def _on_value_change(self, item=None): if self.ignore_value_changes: return From 66d65e85105adfc79e176d04605775d3d8a345e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 11:19:41 +0200 Subject: [PATCH 615/947] global values are updated on save --- .../config_setting/widgets/base.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index b412db5308..afcac74555 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -165,6 +165,16 @@ class StudioWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(origin_values, file_stream, indent=4) + self._update_global_values() + + def _update_global_values(self): + values = {"studio": config.studio_configurations()} + for input_field in self.input_fields: + input_field.update_global_values(values) + + for input_field in self.input_fields: + input_field.hierarchical_style_update() + def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] klass = lib.TypeToKlass.types.get(item_type) @@ -454,7 +464,6 @@ class ProjectWidget(QtWidgets.QWidget): self._save_defaults() else: self._save_overrides() - self._on_project_change() def _save_overrides(self): _data = {} @@ -481,6 +490,8 @@ class ProjectWidget(QtWidgets.QWidget): with open(overrides_json_path, "w") as file_stream: json.dump(output_data, file_stream, indent=4) + self._on_project_change() + def _save_defaults(self): output = {} for item in self.input_fields: @@ -534,3 +545,13 @@ class ProjectWidget(QtWidgets.QWidget): print("Saving data to: ", output_path) with open(output_path, "w") as file_stream: json.dump(origin_values, file_stream, indent=4) + + self._update_global_values() + + def _update_global_values(self): + values = {"project": config.global_project_configurations()} + for input_field in self.input_fields: + input_field.update_global_values(values) + + for input_field in self.input_fields: + input_field.hierarchical_style_update() From b0375abaddf98c569a22e93390b9471047909773 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 15:01:16 +0200 Subject: [PATCH 616/947] removed update_style --- .../config_setting/config_setting/widgets/inputs.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 950fdb5bf8..4df9bb2736 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -317,7 +317,6 @@ class BooleanWidget(ConfigWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -451,7 +450,6 @@ class IntegerWidget(ConfigWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -583,7 +581,6 @@ class FloatWidget(ConfigWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def clear_value(self): self.set_value(0) @@ -704,7 +701,6 @@ class TextSingleLineWidget(ConfigWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def clear_value(self): self.set_value("") @@ -823,7 +819,6 @@ class TextMultiLineWidget(ConfigWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def clear_value(self): self.set_value("") @@ -1005,7 +1000,6 @@ class RawJsonWidget(ConfigWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def _on_value_change(self, item=None): self._is_invalid = self.text_input.has_invalid_value() @@ -1348,7 +1342,6 @@ class ListWidget(ConfigWidget, InputObject): self.value_widget.apply_overrides(override_value) self._is_modified = False self._state = None - self.update_style() def update_style(self): state = self.style_state( @@ -1684,7 +1677,6 @@ class ModifiableDict(ExpandingWidget, InputObject): value = override_value self.set_value(value) - self.update_style() def update_style(self): state = self.style_state( @@ -1803,7 +1795,6 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): ) ) self._was_overriden = bool(self._is_overriden) - self.update_style() def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -2026,7 +2017,7 @@ class DictWidget(ConfigWidget, ConfigObject): or self.child_overriden ) ) - self.update_style() + self._was_overriden = bool(self._is_overriden) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -2290,7 +2281,7 @@ class DictInvisible(ConfigWidget, ConfigObject): or self.child_overriden ) ) - self.update_style() + self._was_overriden = bool(self._is_overriden) def overrides(self): if not self.is_overriden and not self.child_overriden: From 084d9a9a116bc830daf103c4abe9e218da77be10 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 15:02:16 +0200 Subject: [PATCH 617/947] implemented override_value_from_values for override values --- .../config_setting/widgets/inputs.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4df9bb2736..80cb168fda 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -126,6 +126,39 @@ class ConfigObject: value = value[key] return value + def override_value_from_values(self, values, keys=None): + """Global getter of value based on loaded values.""" + if not values or values is AS_WIDGET: + return NOT_SET + + if keys is None: + keys = self.keys + + value = values + if not keys: + return value, False + + parent_value = None + last_key = None + for key in keys: + if not isinstance(value, dict): + raise TypeError( + "Expected dictionary got {}.".format(str(type(value))) + ) + if key not in value: + return NOT_SET, False + + parent_value = value + last_key = key + value = value[key] + + if not parent_value: + return value, False + + metadata = parent_value.get(METADATA_KEY) or {} + groups = metadata.get("group") or tuple() + return value, last_key in groups + def style_state(self, is_invalid, is_overriden, is_modified): items = [] if is_invalid: From 077f5c7163a7bbeea63f8cbcec9b278a27ba1927 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 16:17:56 +0200 Subject: [PATCH 618/947] different logic for applying overrides --- .../config_setting/widgets/base.py | 2 +- .../config_setting/widgets/inputs.py | 276 ++++++------------ 2 files changed, 96 insertions(+), 182 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index afcac74555..9c1fee5b6f 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -428,7 +428,7 @@ class ProjectWidget(QtWidgets.QWidget): _overrides = config.project_configurations_overrides(project_name) self.is_overidable = True - overrides = lib.convert_overrides_to_gui_data(_overrides) + overrides = {"project": lib.convert_overrides_to_gui_data(_overrides)} self.project_name = project_name self.ignore_value_changes = True for item in self.input_fields: diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 80cb168fda..3eeea4df6a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -126,39 +126,6 @@ class ConfigObject: value = value[key] return value - def override_value_from_values(self, values, keys=None): - """Global getter of value based on loaded values.""" - if not values or values is AS_WIDGET: - return NOT_SET - - if keys is None: - keys = self.keys - - value = values - if not keys: - return value, False - - parent_value = None - last_key = None - for key in keys: - if not isinstance(value, dict): - raise TypeError( - "Expected dictionary got {}.".format(str(type(value))) - ) - if key not in value: - return NOT_SET, False - - parent_value = value - last_key = key - value = value[key] - - if not parent_value: - return value, False - - metadata = parent_value.get(METADATA_KEY) or {} - groups = metadata.get("group") or tuple() - return value, last_key in groups - def style_state(self, is_invalid, is_overriden, is_modified): items = [] if is_invalid: @@ -222,6 +189,28 @@ class InputObject(ConfigObject): self._is_modified = False self._was_overriden = False + def apply_overrides(self, parent_values): + self._is_modified = False + self._state = None + + if parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + + self.override_value = override_value + + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + value = self.start_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + def discard_changes(self): if ( self.is_overidable @@ -336,21 +325,6 @@ class BooleanWidget(ConfigWidget, InputObject): def clear_value(self): self.set_value(False) - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - value = self.start_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -469,21 +443,6 @@ class IntegerWidget(ConfigWidget, InputObject): def reset_value(self): self.set_value(self.start_value) - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - value = self.global_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -602,19 +561,6 @@ class FloatWidget(ConfigWidget, InputObject): def reset_value(self): self.set_value(self.global_value) - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - value = self.start_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - def clear_value(self): self.set_value(0) @@ -720,21 +666,6 @@ class TextSingleLineWidget(ConfigWidget, InputObject): def reset_value(self): self.set_value(self.start_value) - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - value = self.start_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - def clear_value(self): self.set_value("") @@ -840,19 +771,6 @@ class TextMultiLineWidget(ConfigWidget, InputObject): def reset_value(self): self.set_value(self.start_value) - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - value = self.start_value - else: - self._is_overriden = True - value = override_value - - self.set_value(value) - def clear_value(self): self.set_value("") @@ -1019,21 +937,6 @@ class RawJsonWidget(ConfigWidget, InputObject): def clear_value(self): self.set_value("") - def apply_overrides(self, override_value): - self._is_modified = False - self._state = None - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - value = self.start_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - def _on_value_change(self, item=None): self._is_invalid = self.text_input.has_invalid_value() if self.ignore_value_changes: @@ -1206,13 +1109,22 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): self.start_value = self.item_value() self._on_value_change() - def apply_overrides(self, override_value): + # TODO use same as InputObject + def apply_overrides(self, parent_values): + if parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + self.override_value = override_value + if override_value is NOT_SET: self._is_overriden = False + self._was_overriden = False value = self.start_value else: self._is_overriden = True + self._was_overriden = True value = override_value self.set_value(value) @@ -1372,9 +1284,9 @@ class ListWidget(ConfigWidget, InputObject): self.value_widget.clear_value() def apply_overrides(self, override_value): - self.value_widget.apply_overrides(override_value) self._is_modified = False self._state = None + self.value_widget.apply_overrides(override_value) def update_style(self): state = self.style_state( @@ -1696,21 +1608,6 @@ class ModifiableDict(ExpandingWidget, InputObject): self.update_style() - def apply_overrides(self, override_value): - self._state = None - self._is_modified = False - self.override_value = override_value - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - value = self.global_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - def update_style(self): state = self.style_state( self.is_invalid, self.is_overriden, self.is_modified @@ -1806,27 +1703,30 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): for item in self.input_fields: item.update_global_values(values) - def apply_overrides(self, override_value): + def apply_overrides(self, parent_values): # Make sure this is set to False - self._is_overriden = False self._state = None self._child_state = None + + metadata = {} + groups = tuple() + override_values = NOT_SET + if parent_values is not NOT_SET: + metadata = parent_values.get(METADATA_KEY) or metadata + groups = metadata.get("groups") or groups + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = self.key in groups + for item in self.input_fields: - if override_value is NOT_SET: - child_value = NOT_SET - else: - child_value = override_value.get(item.key, NOT_SET) + item.apply_overrides(override_values) - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not NOT_SET - or self.child_overriden + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden ) - ) self._was_overriden = bool(self._is_overriden) def _on_value_change(self, item=None): @@ -2029,27 +1929,30 @@ class DictWidget(ConfigWidget, ConfigObject): for item in self.input_fields: item.update_global_values(values) - def apply_overrides(self, override_value): + def apply_overrides(self, parent_values): # Make sure this is set to False - self._is_overriden = False self._state = None self._child_state = None + + metadata = {} + groups = tuple() + override_values = NOT_SET + if parent_values is not NOT_SET: + metadata = parent_values.get(METADATA_KEY) or metadata + groups = metadata.get("groups") or groups + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = self.key in groups + for item in self.input_fields: - if override_value is NOT_SET: - child_value = NOT_SET - else: - child_value = override_value.get(item.key, NOT_SET) + item.apply_overrides(override_values) - item.apply_overrides(child_value) - - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not NOT_SET - or self.child_overriden + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden ) - ) self._was_overriden = bool(self._is_overriden) def _on_value_change(self, item=None): @@ -2297,23 +2200,30 @@ class DictInvisible(ConfigWidget, ConfigObject): for item in self.input_fields: item.update_global_values(values) - def apply_overrides(self, override_value): - self._is_overriden = False - for item in self.input_fields: - if override_value is NOT_SET: - child_value = NOT_SET - else: - child_value = override_value.get(item.key, NOT_SET) - item.apply_overrides(child_value) + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None - self._is_overriden = ( - self.is_group - and self.is_overidable - and ( - override_value is not None - or self.child_overriden + metadata = {} + groups = tuple() + override_values = NOT_SET + if parent_values is not NOT_SET: + metadata = parent_values.get(METADATA_KEY) or metadata + groups = metadata.get("groups") or groups + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = self.key in groups + + for item in self.input_fields: + item.apply_overrides(override_values) + + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden ) - ) self._was_overriden = bool(self._is_overriden) def overrides(self): @@ -2377,6 +2287,10 @@ class DictFormWidget(ConfigWidget, ConfigObject): return super(DictFormWidget, self).mouseReleaseEvent(event) + def apply_overrides(self, parent_values): + for item in self.input_fields.values(): + item.apply_overrides(parent_values) + def discard_changes(self): for item in self.input_fields.values(): item.discard_changes() From b4e091c3fc50166192d3b86aa128e6a63a397660 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 31 Aug 2020 16:45:36 +0200 Subject: [PATCH 619/947] strip dot from repre names in single frame renders --- pype/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index ab5d6cf9b2..758872e717 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -428,7 +428,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "to render, don't know what to do " "with them.") col = rem[0] - _, ext = os.path.splitext(col) + ext = os.path.splitext(col)[1].lstrip(".") else: # but we really expect only one collection. # Nothing else make sense. From 618b2f4825aa87608be50ba4e5491149950a7002 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 17:21:16 +0200 Subject: [PATCH 620/947] added Timers manager to tray modules --- .../config_gui_schema/studio_schema/1_tray_items.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index 45b1bc65ce..4e0e040dfe 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -44,6 +44,10 @@ "type": "boolean", "key": "Idle Manager", "label": "Idle Manager" + }, { + "type": "boolean", + "key": "Timers Manager", + "label": "Timers Manager" }, { "type": "boolean", "key": "Rest Api", From 1d72f9d01310d5ee72c6e5a51d875f13157c09ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 17:22:18 +0200 Subject: [PATCH 621/947] fix method name --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3eeea4df6a..e054f918cd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1036,7 +1036,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def on_remove_clicked(self): if self.is_single: - self.value_input.clear() + self.value_input.clear_value() else: self.parent().remove_row(self) From 8276c7397154f3423dd184c54aee642b7770110f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 17:22:28 +0200 Subject: [PATCH 622/947] added exclude ports to schema --- .../config_gui_schema/studio_schema/1_tray_items.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index 4e0e040dfe..849019c89e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -76,6 +76,15 @@ "label": "Default Port", "minimum": 1, "maximum": 65535 + }, { + "type": "list", + "object_type": "int", + "key": "exclude_ports", + "label": "Exclude ports", + "input_modifiers": { + "minimum": 1, + "maximum": 65535 + } } ] }, { From 85df0e3dbbb507d6eab569e6403401dca0f34484 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 17:59:24 +0200 Subject: [PATCH 623/947] item widget in list is after buttons --- .../config_setting/widgets/inputs.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e054f918cd..5d41ce836c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -993,15 +993,6 @@ class ListItem(QtWidgets.QWidget, ConfigObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) - ItemKlass = TypeToKlass.types[object_type] - self.value_input = ItemKlass( - input_modifiers, - AS_WIDGET, - [], - self, - None - ) - self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") @@ -1014,13 +1005,22 @@ class ListItem(QtWidgets.QWidget, ConfigObject): self.add_btn.setProperty("btn-type", "tool-item") self.remove_btn.setProperty("btn-type", "tool-item") - layout.addWidget(self.value_input, 1) layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) self.add_btn.clicked.connect(self.on_add_clicked) self.remove_btn.clicked.connect(self.on_remove_clicked) + ItemKlass = TypeToKlass.types[object_type] + self.value_input = ItemKlass( + input_modifiers, + AS_WIDGET, + [], + self, + None + ) + layout.addWidget(self.value_input, 1) + self.value_input.value_changed.connect(self._on_value_change) self.is_single = False From 2e8abd02cb29511726b6f860511b4b31a4dc01a9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 18:00:57 +0200 Subject: [PATCH 624/947] empty list is shown as disabled first item --- .../config_setting/widgets/inputs.py | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 5d41ce836c..d197e85d1e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1023,7 +1023,9 @@ class ListItem(QtWidgets.QWidget, ConfigObject): self.value_input.value_changed.connect(self._on_value_change) - self.is_single = False + def set_as_empty(self, is_empty=True): + self.value_input.setEnabled(not is_empty) + self.remove_btn.setEnabled(not is_empty) def _on_value_change(self, item=None): self.value_changed.emit(self) @@ -1032,16 +1034,18 @@ class ListItem(QtWidgets.QWidget, ConfigObject): return self.parent().input_fields.index(self) def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) + if self.value_input.isEnabled(): + self.parent().add_row(row=self.row() + 1) + else: + self.set_as_empty(False) def on_remove_clicked(self): - if self.is_single: - self.value_input.clear_value() - else: - self.parent().remove_row(self) + self.parent().remove_row(self) def config_value(self): - return self.value_input.item_value() + if self.value_input.isEnabled(): + return self.value_input.item_value() + return NOT_SET # TODO Move subwidget to main widget @@ -1085,12 +1089,12 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): for item_value in self.default_value: self.add_row(value=item_value) - if self.count() == 0: - self.add_row() - for old_input in old_inputs: self.remove_row(old_input) + if self.count() == 0: + self.add_row(is_empty=True) + self.global_value = value self.start_value = self.item_value() @@ -1141,18 +1145,11 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): def count(self): return len(self.input_fields) - def add_row(self, row=None, value=None): + def add_row(self, row=None, value=None, is_empty=False): # Create new item item_widget = ListItem(self.object_type, self.input_modifiers, self) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False - + if is_empty: + item_widget.set_as_empty() item_widget.value_changed.connect(self._on_value_change) if row is None: @@ -1186,12 +1183,8 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): item_widget.setParent(None) item_widget.deleteLater() - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True + if self.count() == 0: + self.add_row(is_empty=True) self._on_value_change() self.parent().updateGeometry() @@ -1199,9 +1192,9 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): def item_value(self): output = [] for item in self.input_fields: - text = item.config_value() - if text: - output.append(text) + value = item.config_value() + if value is not NOT_SET: + output.append(value) return output From ed2d4175ed22d6139f4270f7cbc78860dc22a0fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 18:08:07 +0200 Subject: [PATCH 625/947] added color for disabled widgets --- pype/tools/config_setting/config_setting/style/style.css | 4 ++++ pype/tools/config_setting/config_setting/widgets/inputs.py | 1 + 2 files changed, 5 insertions(+) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index c575ed64c3..b39eaf0802 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -5,6 +5,10 @@ QWidget { border-radius: 0px; } +QWidget:disabled { + background-color: #4e6474; +} + QMenu { border: 1px solid #555555; background-color: #1d272f; diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d197e85d1e..08bc90766f 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1026,6 +1026,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def set_as_empty(self, is_empty=True): self.value_input.setEnabled(not is_empty) self.remove_btn.setEnabled(not is_empty) + self._on_value_change() def _on_value_change(self, item=None): self.value_changed.emit(self) From 8bca9da7902cdf3422d7f3dd35b8a132d85d309c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 18:45:25 +0200 Subject: [PATCH 626/947] removed ConfigWidget --- .../config_setting/widgets/inputs.py | 66 +++++++++++++++---- .../config_setting/widgets/widgets.py | 40 +---------- 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 08bc90766f..f603514784 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2,7 +2,6 @@ import json import logging from Qt import QtWidgets, QtCore, QtGui from .widgets import ( - ConfigWidget, ExpandingWidget, ModifiedIntSpinBox, ModifiedFloatSpinBox @@ -11,6 +10,8 @@ from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass class ConfigObject: + allow_actions = True + default_state = "" _is_overriden = False _is_modified = False @@ -173,6 +174,49 @@ class ConfigObject: "Method `hierarchical_style_update` not implemented!" ) + def mouseReleaseEvent(self, event): + if self.allow_actions and event.button() == QtCore.Qt.RightButton: + menu = QtWidgets.QMenu() + + actions_mapping = {} + if self.child_modified: + action = QtWidgets.QAction("Discard changes") + actions_mapping[action] = self._discard_changes + menu.addAction(action) + + if ( + not self.any_parent_overriden() + and (self.is_overriden or self.child_overriden) + ): + # TODO better label + action = QtWidgets.QAction("Remove override") + actions_mapping[action] = self._remove_overrides + menu.addAction(action) + + if not actions_mapping: + action = QtWidgets.QAction("< No action >") + actions_mapping[action] = None + menu.addAction(action) + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + to_run = actions_mapping[result] + if to_run: + to_run() + return + + mro = type(self).mro() + index = mro.index(self.__class__) + item = None + for idx in range(index + 1, len(mro)): + _item = mro[idx] + if hasattr(_item, "mouseReleaseEvent"): + item = _item + break + + if item: + return item.mouseReleaseEvent(self, event) + class InputObject(ConfigObject): def overrides(self): @@ -251,7 +295,7 @@ class InputObject(ConfigObject): return -class BooleanWidget(ConfigWidget, InputObject): +class BooleanWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -365,7 +409,7 @@ class BooleanWidget(ConfigWidget, InputObject): return self.checkbox.isChecked() -class IntegerWidget(ConfigWidget, InputObject): +class IntegerWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) input_modifiers = ("minimum", "maximum") @@ -476,7 +520,7 @@ class IntegerWidget(ConfigWidget, InputObject): return self.int_input.value() -class FloatWidget(ConfigWidget, InputObject): +class FloatWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) input_modifiers = ("minimum", "maximum", "decimal") @@ -597,7 +641,7 @@ class FloatWidget(ConfigWidget, InputObject): return self.float_input.value() -class TextSingleLineWidget(ConfigWidget, InputObject): +class TextSingleLineWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -702,7 +746,7 @@ class TextSingleLineWidget(ConfigWidget, InputObject): return self.text_input.text() -class TextMultiLineWidget(ConfigWidget, InputObject): +class TextMultiLineWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -859,7 +903,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): super(RawJsonInput, self).resizeEvent(event) -class RawJsonWidget(ConfigWidget, InputObject): +class RawJsonWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -1200,7 +1244,7 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): return output -class ListWidget(ConfigWidget, InputObject): +class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -1841,7 +1885,7 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): return {self.key: values}, self.is_group -class DictWidget(ConfigWidget, ConfigObject): +class DictWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__( @@ -2068,7 +2112,7 @@ class DictWidget(ConfigWidget, ConfigObject): return {self.key: values}, self.is_group -class DictInvisible(ConfigWidget, ConfigObject): +class DictInvisible(QtWidgets.QWidget, ConfigObject): # TODO is not overridable by itself value_changed = QtCore.Signal(object) allow_actions = False @@ -2244,7 +2288,7 @@ class FormLabel(QtWidgets.QLabel): # Proxy for form layout -class DictFormWidget(ConfigWidget, ConfigObject): +class DictFormWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) allow_actions = False diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index b824ea8720..e551d1e9c3 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets, QtCore class ModifiedIntSpinBox(QtWidgets.QSpinBox): @@ -48,43 +48,7 @@ class ClickableWidget(QtWidgets.QLabel): super(ClickableWidget, self).mouseReleaseEvent(event) -class ConfigWidget(QtWidgets.QWidget): - allow_actions = True - - def mouseReleaseEvent(self, event): - if self.allow_actions and event.button() == QtCore.Qt.RightButton: - menu = QtWidgets.QMenu() - - actions_mapping = {} - if self.child_modified: - action = QtWidgets.QAction("Discard changes") - actions_mapping[action] = self._discard_changes - menu.addAction(action) - - if ( - not self.any_parent_overriden() - and (self.is_overriden or self.child_overriden) - ): - # TODO better label - action = QtWidgets.QAction("Remove override") - actions_mapping[action] = self._remove_overrides - menu.addAction(action) - - if not actions_mapping: - action = QtWidgets.QAction("< No action >") - actions_mapping[action] = None - menu.addAction(action) - - result = menu.exec_(QtGui.QCursor.pos()) - if result: - to_run = actions_mapping[result] - if to_run: - to_run() - return - super(ConfigWidget, self).mouseReleaseEvent(event) - - -class ExpandingWidget(ConfigWidget): +class ExpandingWidget(QtWidgets.QWidget): def __init__(self, label, parent): super(ExpandingWidget, self).__init__(parent) self.setObjectName("ExpandingWidget") From e7875892e992105d0698d0f2b510914408d63537 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 18:45:47 +0200 Subject: [PATCH 627/947] added better disabled style --- pype/tools/config_setting/config_setting/style/style.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index b39eaf0802..996b73b9cd 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -5,10 +5,6 @@ QWidget { border-radius: 0px; } -QWidget:disabled { - background-color: #4e6474; -} - QMenu { border: 1px solid #555555; background-color: #1d272f; @@ -36,6 +32,10 @@ QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit { background-color: #1d272f; } +QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QPlainTextEdit:disabled, QTextEdit:disabled, QPushButton:disabled { + background-color: #4e6474; +} + QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus { border: 1px solid #ffffff; } From a1d3616bb59d12d52c3b7c2bc97ced31d6a6c4ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 19:23:56 +0200 Subject: [PATCH 628/947] removed list subwidget --- .../config_setting/widgets/inputs.py | 200 +++++++----------- 1 file changed, 74 insertions(+), 126 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f603514784..e4a1fba096 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1093,26 +1093,49 @@ class ListItem(QtWidgets.QWidget, ConfigObject): return NOT_SET -# TODO Move subwidget to main widget -class ListSubWidget(QtWidgets.QWidget, ConfigObject): +class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) - def __init__(self, input_data, values, parent_keys, parent): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): self._parent = parent - super(ListSubWidget, self).__init__(parent) - self.setObjectName("ListSubWidget") + super(ListWidget, self).__init__(parent) + self.setObjectName("ListWidget") - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 5, 0, 5) - layout.setSpacing(3) - self.setLayout(layout) + self._state = None + self.is_group = input_data.get("is_group", False) - self.input_fields = [] self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} + self.input_fields = [] + + inputs_widget = QtWidgets.QWidget(self) + inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + + inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) + inputs_layout.setContentsMargins(0, 5, 0, 5) + inputs_layout.setSpacing(3) + + self.inputs_widget = inputs_widget + self.inputs_layout = inputs_layout + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + + self.label_widget = label_widget + + layout.addWidget(inputs_widget) + self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) @@ -1122,10 +1145,22 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): self.override_value = NOT_SET + def count(self): + return len(self.input_fields) + + def reset_value(self): + self.set_value(self.start_value) + + def clear_value(self): + self.set_value([]) + def update_global_values(self, values): old_inputs = tuple(self.input_fields) value = self.value_from_values(values) + + self.global_value = value + if value is not NOT_SET: for item_value in value: self.add_row(value=item_value) @@ -1140,7 +1175,6 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): if self.count() == 0: self.add_row(is_empty=True) - self.global_value = value self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value @@ -1158,37 +1192,16 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): self.start_value = self.item_value() self._on_value_change() - # TODO use same as InputObject - def apply_overrides(self, parent_values): - if parent_values is NOT_SET or self.key not in parent_values: - override_value = NOT_SET - else: - override_value = parent_values[self.key] - - self.override_value = override_value - - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - value = self.start_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - - def reset_value(self): - self.set_value(self.start_value) - - def clear_value(self): - self.set_value([]) - def _on_value_change(self, item=None): - self.value_changed.emit(self) + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.global_value + if self.is_overidable: + self._is_overriden = True - def count(self): - return len(self.input_fields) + self.update_style() + + self.value_changed.emit(self) def add_row(self, row=None, value=None, is_empty=False): # Create new item @@ -1234,97 +1247,27 @@ class ListSubWidget(QtWidgets.QWidget, ConfigObject): self._on_value_change() self.parent().updateGeometry() - def item_value(self): - output = [] - for item in self.input_fields: - value = item.config_value() - if value is not NOT_SET: - output.append(value) + def apply_overrides(self, parent_values): + if parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] - return output + self.override_value = override_value - -class ListWidget(QtWidgets.QWidget, InputObject): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - - self.is_group = input_data.get("is_group", False) - - self._state = None - - super(ListWidget, self).__init__(parent) - self.setObjectName("ListWidget") - - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget) - - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.value_widget = ListSubWidget( - input_data, values, parent_keys, self - ) - self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - - layout.addWidget(self.value_widget) - self.setLayout(layout) - - self.value_widget.value_changed.connect(self._on_value_change) - - def update_global_values(self, values): - self.value_widget.update_global_values(values) - - @property - def start_value(self): - return self.value_widget.start_value - - @property - def global_value(self): - return self.value_widget.global_value - - @property - def override_value(self): - return self.value_widget.override_value - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - self._is_modified = self.item_value() != self.global_value - if self.is_overidable: + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + value = self.start_value + else: self._is_overriden = True + self._was_overriden = True + value = override_value - self.update_style() - - self.value_changed.emit(self) - - def set_value(self, value, *, global_value=False): - self.value_widget.set_value(value, global_value=global_value) - if global_value: - self._on_value_change() - - def reset_value(self): - self.value_widget.reset_value() - - def clear_value(self): - self.value_widget.clear_value() - - def apply_overrides(self, override_value): self._is_modified = False self._state = None - self.value_widget.apply_overrides(override_value) + + self.set_value(value) def update_style(self): state = self.style_state( @@ -1337,7 +1280,12 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.label_widget.style().polish(self.label_widget) def item_value(self): - return self.value_widget.item_value() + output = [] + for item in self.input_fields: + value = item.config_value() + if value is not NOT_SET: + output.append(value) + return output class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): From 0f1dc4271ff5da6d1b67a6a99380edaf2745795e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 19:28:04 +0200 Subject: [PATCH 629/947] list changes cleanup --- .../config_setting/widgets/inputs.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e4a1fba096..6c33ffacc0 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1028,8 +1028,8 @@ class ListItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, object_type, input_modifiers, parent): - self._parent = parent + def __init__(self, object_type, input_modifiers, config_parent, parent): + self._parent = config_parent super(ListItem, self).__init__(parent) @@ -1076,16 +1076,16 @@ class ListItem(QtWidgets.QWidget, ConfigObject): self.value_changed.emit(self) def row(self): - return self.parent().input_fields.index(self) + return self._parent.input_fields.index(self) def on_add_clicked(self): if self.value_input.isEnabled(): - self.parent().add_row(row=self.row() + 1) + self._parent.add_row(row=self.row() + 1) else: self.set_as_empty(False) def on_remove_clicked(self): - self.parent().remove_row(self) + self._parent.remove_row(self) def config_value(self): if self.value_input.isEnabled(): @@ -1205,16 +1205,18 @@ class ListWidget(QtWidgets.QWidget, InputObject): def add_row(self, row=None, value=None, is_empty=False): # Create new item - item_widget = ListItem(self.object_type, self.input_modifiers, self) + item_widget = ListItem( + self.object_type, self.input_modifiers, self, self.inputs_widget + ) if is_empty: item_widget.set_as_empty() item_widget.value_changed.connect(self._on_value_change) if row is None: - self.layout().addWidget(item_widget) + self.inputs_layout.addWidget(item_widget) self.input_fields.append(item_widget) else: - self.layout().insertWidget(row, item_widget) + self.inputs_layout.insertWidget(row, item_widget) self.input_fields.insert(row, item_widget) previous_input = None @@ -1231,12 +1233,12 @@ class ListWidget(QtWidgets.QWidget, InputObject): item_widget.value_input.set_value(value, global_value=True) else: self._on_value_change() - self.parent().updateGeometry() + self.updateGeometry() def remove_row(self, item_widget): item_widget.value_changed.disconnect() - self.layout().removeWidget(item_widget) + self.inputs_layout.removeWidget(item_widget) self.input_fields.remove(item_widget) item_widget.setParent(None) item_widget.deleteLater() @@ -1245,7 +1247,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.add_row(is_empty=True) self._on_value_change() - self.parent().updateGeometry() + self.updateGeometry() def apply_overrides(self, parent_values): if parent_values is NOT_SET or self.key not in parent_values: From 7849c707e1a756a8e6605eb3ad367a46896af6eb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 19:33:26 +0200 Subject: [PATCH 630/947] added possibility to set key as overriden --- .../config_setting/widgets/inputs.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 6c33ffacc0..208bfa6685 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -169,6 +169,14 @@ class ConfigObject: ) ) + def _set_as_overriden(self): + self.ignore_value_changes = True + self.set_as_overriden() + self.ignore_value_changes = False + + def set_as_overriden(self): + self._is_overriden = True + def hierarchical_style_update(self): raise NotImplementedError( "Method `hierarchical_style_update` not implemented!" @@ -184,6 +192,15 @@ class ConfigObject: actions_mapping[action] = self._discard_changes menu.addAction(action) + if ( + self.is_overidable + and not self.is_overriden + and not self.any_parent_is_group + ): + action = QtWidgets.QAction("Set as overriden") + actions_mapping[action] = self._set_as_overriden + menu.addAction(action) + if ( not self.any_parent_overriden() and (self.is_overriden or self.child_overriden) From 1ab9162b3b94e1d757bb62c3377263d45883504e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 19:55:50 +0200 Subject: [PATCH 631/947] is group is abstract attribute --- .../config_setting/widgets/inputs.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 208bfa6685..d8121367e3 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -13,10 +13,13 @@ class ConfigObject: allow_actions = True default_state = "" + _is_overriden = False _is_modified = False _was_overriden = False _is_invalid = False + _is_group = False + _log = None @property @@ -45,6 +48,11 @@ class ConfigObject: """Value set in is not valid.""" return self._is_invalid + @property + def is_group(self): + """Value set in is not valid.""" + return self._is_group + @property def is_overidable(self): """Should care about overrides.""" @@ -321,7 +329,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -436,7 +444,7 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -547,7 +555,7 @@ class FloatWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -667,7 +675,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -772,7 +780,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -929,7 +937,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -1122,7 +1130,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.setObjectName("ListWidget") self._state = None - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) @@ -1572,7 +1580,7 @@ class ModifiableDict(ExpandingWidget, InputObject): self.any_parent_is_group = any_parent_is_group - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self._state = None super(ModifiableDict, self).__init__(input_data["label"], parent) @@ -1656,7 +1664,7 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self._state = None self._child_state = None @@ -1870,7 +1878,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) self._state = None self._child_state = None @@ -2094,7 +2102,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): any_parent_is_group = parent.any_parent_is_group self.any_parent_is_group = any_parent_is_group - self.is_group = input_data.get("is_group", False) + self._is_group = input_data.get("is_group", False) super(DictInvisible, self).__init__(parent) self.setObjectName("DictInvisible") @@ -2270,7 +2278,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group - self.is_group = False + self._is_group = False super(DictFormWidget, self).__init__(parent) From 724fe35c84aff0cb00503d4f3fc5f88e0b0766d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 19:56:07 +0200 Subject: [PATCH 632/947] set as overriden is abstract method --- .../config_setting/widgets/inputs.py | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d8121367e3..1261f3b91b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -183,7 +183,9 @@ class ConfigObject: self.ignore_value_changes = False def set_as_overriden(self): - self._is_overriden = True + raise NotImplementedError( + "Method `set_as_overriden` not implemented!" + ) def hierarchical_style_update(self): raise NotImplementedError( @@ -298,6 +300,9 @@ class InputObject(ConfigObject): self._is_modified = False self._is_overriden = self._was_overriden + def set_as_overriden(self): + self._is_overriden = True + @property def child_modified(self): return self.is_modified @@ -1712,6 +1717,17 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + def update_global_values(self, values): for item in self.input_fields: item.update_global_values(values) @@ -1938,6 +1954,17 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + def update_global_values(self, values): for item in self.input_fields: item.update_global_values(values) @@ -2209,6 +2236,17 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + def update_global_values(self, values): for item in self.input_fields: item.update_global_values(values) @@ -2311,6 +2349,17 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + def update_global_values(self, values): for item in self.input_fields.values(): item.update_global_values(values) From e7a451775611dfe0e14039f2db110e4404a3673d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 20:09:01 +0200 Subject: [PATCH 633/947] fixed determination of overriden keys --- .../tools/config_setting/config_setting/widgets/inputs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 1261f3b91b..0c35f7d10d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1821,7 +1821,7 @@ class DictExpandWidget(ExpandingWidget, ConfigObject): @property def child_overriden(self): for input_field in self.input_fields: - if input_field.child_overriden: + if input_field.is_overriden or input_field.child_overriden: return True return False @@ -2059,7 +2059,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): @property def child_overriden(self): for input_field in self.input_fields: - if input_field.child_overriden: + if input_field.is_overriden or input_field.child_overriden: return True return False @@ -2162,7 +2162,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): @property def child_overriden(self): for input_field in self.input_fields: - if input_field.child_overriden: + if input_field.is_overriden or input_field.child_overriden: return True return False @@ -2379,7 +2379,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): @property def child_overriden(self): for input_field in self.input_fields.values(): - if input_field.child_overriden: + if input_field.is_overriden or input_field.child_overriden: return True return False From d7f48119ed3b7966a413872882b5f3fde38cdb13 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 20:09:55 +0200 Subject: [PATCH 634/947] added testing function --- .../config_setting/config_setting/widgets/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/tests.py b/pype/tools/config_setting/config_setting/widgets/tests.py index 16c97a85ef..fc53e38ad5 100644 --- a/pype/tools/config_setting/config_setting/widgets/tests.py +++ b/pype/tools/config_setting/config_setting/widgets/tests.py @@ -1,6 +1,17 @@ from Qt import QtWidgets, QtCore +def indented_print(data, indent=0): + spaces = " " * (indent * 4) + if not isinstance(data, dict): + print("{}{}".format(spaces, data)) + return + + for key, value in data.items(): + print("{}{}".format(spaces, key)) + indented_print(value, indent + 1) + + class SelectableMenu(QtWidgets.QMenu): selection_changed = QtCore.Signal() From 5986041bf0caaa37be01d6b7175adb5bf38b3949 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 20:26:22 +0200 Subject: [PATCH 635/947] get rid of subwidget of modifiable dictionary --- .../config_setting/widgets/inputs.py | 262 ++++++++---------- 1 file changed, 122 insertions(+), 140 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0c35f7d10d..91882de958 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1324,8 +1324,8 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, object_type, input_modifiers, parent): - self._parent = parent + def __init__(self, object_type, input_modifiers, config_parent, parent): + self._parent = config_parent super(ModifiableDictItem, self).__init__(parent) @@ -1413,17 +1413,17 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.key_input.style().polish(self.key_input) def row(self): - return self.parent().input_fields.index(self) + return self._parent.input_fields.index(self) def on_add_clicked(self): - self.parent().add_row(row=self.row() + 1) + self._parent.add_row(row=self.row() + 1) def on_remove_clicked(self): if self.is_single: self.value_input.clear_value() self.key_input.setText("") else: - self.parent().remove_row(self) + self._parent.remove_row(self) def config_value(self): key = self.key_input.text() @@ -1433,22 +1433,44 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): return {key: value} -# TODO Move subwidget to main widget -class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): +class ModifiableDict(ExpandingWidget, InputObject): + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) - def __init__(self, input_data, values, parent_keys, parent): + def __init__( + self, input_data, values, parent_keys, parent, + label_widget=None + ): self._parent = parent - super(ModifiableDictSubWidget, self).__init__(parent) - self.setObjectName("ModifiableDictSubWidget") + super(ModifiableDict, self).__init__(input_data["label"], parent) + self.setObjectName("ModifiableDict") - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 0, 5) - layout.setSpacing(5) - self.setLayout(layout) + self._state = None self.input_fields = [] + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + + self._is_group = input_data.get("is_group", False) + + inputs_widget = QtWidgets.QWidget(self) + inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + + inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) + inputs_layout.setContentsMargins(5, 5, 0, 5) + inputs_layout.setSpacing(5) + + self.set_content_widget(inputs_widget) + + self.inputs_widget = inputs_widget + self.inputs_layout = inputs_layout + self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} @@ -1458,12 +1480,20 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): keys.append(self.key) self.keys = keys + self.override_value = NOT_SET + self.update_global_values(values) + def count(self): + return len(self.input_fields) + def update_global_values(self, values): old_inputs = tuple(self.input_fields) value = self.value_from_values(values) + + self.global_value = value + if value is not NOT_SET: for item_key, item_value in value.items(): self.add_row(key=item_key, value=item_value) @@ -1478,137 +1508,22 @@ class ModifiableDictSubWidget(QtWidgets.QWidget, ConfigObject): for old_input in old_inputs: self.remove_row(old_input) - self.global_value = value - self.start_value = self.config_value() + self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value - @property - def is_group(self): - return self._parent.is_group + def set_value(self, value, *, global_value=False): + previous_inputs = tuple(self.input_fields) + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_value) - @property - def any_parent_is_group(self): - return self._parent.any_parent_is_group + for input_field in previous_inputs: + self.remove_row(input_field) - def _on_value_change(self, item=None): - self.value_changed.emit(self) - - def count(self): - return len(self.input_fields) - - def add_row(self, row=None, key=None, value=None): - # Create new item - item_widget = ModifiableDictItem( - self.object_type, self.input_modifiers, self - ) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False - - item_widget.value_changed.connect(self._on_value_change) - - if row is None: - self.layout().addWidget(item_widget) - self.input_fields.append(item_widget) - else: - self.layout().insertWidget(row, item_widget) - self.input_fields.insert(row, item_widget) - - previous_input = None - for input_field in self.input_fields: - if previous_input is not None: - self.setTabOrder( - previous_input, input_field.key_input - ) - previous_input = input_field.value_input.focusProxy() - self.setTabOrder( - input_field.key_input, previous_input - ) - - # Set value if entered value is not None - # else (when add button clicked) trigger `_on_value_change` - if value is not None and key is not None: - item_widget.default_key = key - item_widget.key_input.setText(key) - item_widget.value_input.set_value(value, global_value=True) - else: + if global_value: + self.global_value = value + self.start_value = self.item_value() self._on_value_change() - self.parent().updateGeometry() - - def remove_row(self, item_widget): - item_widget.value_changed.disconnect() - - self.layout().removeWidget(item_widget) - self.input_fields.remove(item_widget) - item_widget.setParent(None) - item_widget.deleteLater() - - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True - - self._on_value_change() - self.parent().updateGeometry() - - def config_value(self): - output = {} - for item in self.input_fields: - item_value = item.config_value() - if item_value: - output.update(item_value) - return output - - -class ModifiableDict(ExpandingWidget, InputObject): - # Should be used only for dictionary with one datatype as value - # TODO this is actually input field (do not care if is group or not) - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, - label_widget=None - ): - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self.any_parent_is_group = any_parent_is_group - - self._is_group = input_data.get("is_group", False) - self._state = None - - super(ModifiableDict, self).__init__(input_data["label"], parent) - self.setObjectName("ModifiableDict") - - self.value_widget = ModifiableDictSubWidget( - input_data, values, parent_keys, self - ) - self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - self.value_widget.value_changed.connect(self._on_value_change) - - self.set_content_widget(self.value_widget) - - self.key = input_data["key"] - - self.override_value = NOT_SET - self.start_value = self.value_widget.start_value - self.global_value = self.value_widget.global_value - - self._is_modified = self.global_value != self.start_value - - def update_global_values(self, values): - self.value_widget.update_global_values(values) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -1647,7 +1562,74 @@ class ModifiableDict(ExpandingWidget, InputObject): self._state = state def item_value(self): - return self.value_widget.config_value() + output = {} + for item in self.input_fields: + item_value = item.config_value() + if item_value: + output.update(item_value) + return output + + def add_row(self, row=None, key=None, value=None): + # Create new item + item_widget = ModifiableDictItem( + self.object_type, self.input_modifiers, self, self.inputs_widget + ) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.inputs_layout.addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.inputs_layout.insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + previous_input = None + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.key_input + ) + previous_input = input_field.value_input.focusProxy() + self.setTabOrder( + input_field.key_input, previous_input + ) + + # Set value if entered value is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None and key is not None: + item_widget.default_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, global_value=True) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.inputs_layout.removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() # Dictionaries From 416de13fe270b4512609c4da58a48a212881ee06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 20:34:33 +0200 Subject: [PATCH 636/947] fixed form group for overrides --- .../config_setting/config_setting/widgets/inputs.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 91882de958..873481e352 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2331,6 +2331,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + for item in self.input_fields.values(): + item.remove_overrides() + def set_as_overriden(self): if self.is_overriden: return @@ -2350,6 +2357,8 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): if self.ignore_value_changes: return self.value_changed.emit(self) + if self.any_parent_is_group: + self.hierarchical_style_update() @property def child_modified(self): @@ -2419,7 +2428,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): values = {} groups = [] - for input_field in self.input_fields: + for input_field in self.input_fields.values(): value, is_group = input_field.overrides() if value is not NOT_SET: values.update(value) @@ -2427,7 +2436,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): groups.extend(value.keys()) if groups: values[METADATA_KEY] = {"groups": groups} - return {self.key: values}, self.is_group + return values, self.is_group TypeToKlass.types["boolean"] = BooleanWidget From 008204f7d9b3a30f77392b17da2d38aacb9a9f43 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 31 Aug 2020 20:35:14 +0200 Subject: [PATCH 637/947] added form widget to overridable configurations --- .../projects_schema/1_plugins_gui_schema.json | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index c70daab32c..bed839e6dc 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -21,40 +21,45 @@ "is_group": true, "children": [ { - "type": "boolean", - "key": "enabled", - "label": "Enabled", - "default": true - }, { - "type": "text-singleline", - "key": "deadline_department", - "label": "Deadline apartment", - "default": "" - }, { - "type": "int", - "key": "deadline_priority", - "label": "Deadline priority", - "default": 50 - }, { - "type": "text-singleline", - "key": "deadline_pool", - "label": "Deadline pool", - "default": "" - }, { - "type": "text-singleline", - "key": "deadline_pool_secondary", - "label": "Deadline pool (secondary)", - "default": "" - }, { - "type": "text-singleline", - "key": "deadline_group", - "label": "Deadline Group", - "default": "" - }, { - "type": "int", - "key": "deadline_chunk_size", - "label": "Deadline Chunk size", - "default": 10 + "type": "dict-form", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": true + }, { + "type": "text-singleline", + "key": "deadline_department", + "label": "Deadline apartment", + "default": "" + }, { + "type": "int", + "key": "deadline_priority", + "label": "Deadline priority", + "default": 50 + }, { + "type": "text-singleline", + "key": "deadline_pool", + "label": "Deadline pool", + "default": "" + }, { + "type": "text-singleline", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)", + "default": "" + }, { + "type": "text-singleline", + "key": "deadline_group", + "label": "Deadline Group", + "default": "" + }, { + "type": "int", + "key": "deadline_chunk_size", + "label": "Deadline Chunk size", + "default": 10 + } + ] } ] } From 2e1dbec431df446c941b297694fe695e3643443f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 09:45:51 +0200 Subject: [PATCH 638/947] added any_parent_is_group to RawJson --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 873481e352..82befa3276 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -942,6 +942,12 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self.any_parent_is_group = any_parent_is_group + self._is_group = input_data.get("is_group", False) self.default_value = input_data.get("default", NOT_SET) From 1cafdb0aa89e0fd8c6fcb1deadd53ecb1afad0f9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 09:46:10 +0200 Subject: [PATCH 639/947] input_fields in FormWidget are in list not dict --- .../config_setting/widgets/inputs.py | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 82befa3276..8c9e4a054b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2308,7 +2308,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): super(DictFormWidget, self).__init__(parent) - self.input_fields = {} + self.input_fields = [] self.content_layout = QtWidgets.QFormLayout(self) self.keys = list(parent_keys) @@ -2327,11 +2327,11 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): super(DictFormWidget, self).mouseReleaseEvent(event) def apply_overrides(self, parent_values): - for item in self.input_fields.values(): + for item in self.input_fields: item.apply_overrides(parent_values) def discard_changes(self): - for item in self.input_fields.values(): + for item in self.input_fields: item.discard_changes() self._is_modified = self.child_modified @@ -2341,7 +2341,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self._is_overriden = False self._is_modified = False self._was_overriden = False - for item in self.input_fields.values(): + for item in self.input_fields: item.remove_overrides() def set_as_overriden(self): @@ -2356,7 +2356,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): item.set_as_overriden() def update_global_values(self, values): - for item in self.input_fields.values(): + for item in self.input_fields: item.update_global_values(values) def _on_value_change(self, item=None): @@ -2368,34 +2368,33 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): @property def child_modified(self): - for input_field in self.input_fields.values(): + for input_field in self.input_fields: if input_field.child_modified: return True return False @property def child_overriden(self): - for input_field in self.input_fields.values(): + for input_field in self.input_fields: if input_field.is_overriden or input_field.child_overriden: return True return False @property def child_invalid(self): - for input_field in self.input_fields.values(): + for input_field in self.input_fields: if input_field.child_invalid: return True return False def get_invalid(self): output = [] - for input_field in self.input_fields.values(): + for input_field in self.input_fields: output.extend(input_field.get_invalid()) return output def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] - key = child_configuration["key"] # Pop label to not be set in child label = child_configuration["label"] @@ -2410,16 +2409,16 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): item.value_changed.connect(self._on_value_change) self.content_layout.addRow(label_widget, item) - self.input_fields[key] = item + self.input_fields.append(item) return item def hierarchical_style_update(self): - for input_field in self.input_fields.values(): + for input_field in self.input_fields: input_field.hierarchical_style_update() def item_value(self): output = {} - for input_field in self.input_fields.values(): + for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts output.update(input_field.config_value()) @@ -2434,7 +2433,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): values = {} groups = [] - for input_field in self.input_fields.values(): + for input_field in self.input_fields: value, is_group = input_field.overrides() if value is not NOT_SET: values.update(value) From bfc4a7d595bd152a67a585a8c23e6fa3b629fdfd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 10:10:59 +0200 Subject: [PATCH 640/947] DictWidget can be also expandable --- .../config_setting/widgets/inputs.py | 85 +++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8c9e4a054b..8e66a5b1c2 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1874,50 +1874,22 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): raise TypeError("Can't use \"{}\" as widget item.".format( self.__class__.__name__ )) + + super(DictWidget, self).__init__(parent) + self.setObjectName("DictWidget") + + self._state = None + self._child_state = None + self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) - self._state = None - self._child_state = None - - super(DictWidget, self).__init__(parent) - self.setObjectName("DictWidget") - - body_widget = QtWidgets.QWidget(self) - - label_widget = QtWidgets.QLabel( - input_data["label"], parent=body_widget - ) - label_widget.setObjectName("DictLabel") - - content_widget = QtWidgets.QWidget(body_widget) - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 0, 3) - - body_layout = QtWidgets.QVBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(5) - body_layout.addWidget(label_widget) - body_layout.addWidget(content_widget) - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(5, 5, 0, 5) - main_layout.setSpacing(0) - main_layout.addWidget(body_widget) - - self.label_widget = label_widget - self.content_widget = content_widget - self.content_layout = content_layout - self.input_fields = [] self.key = input_data["key"] @@ -1925,6 +1897,49 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): keys.append(self.key) self.keys = keys + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setSpacing(0) + + expandable = input_data.get("expandable", True) + if expandable: + main_layout.setContentsMargins(0, 0, 0, 0) + body_widget = ExpandingWidget(input_data["label"], self) + else: + main_layout.setContentsMargins(5, 5, 0, 5) + body_widget = QtWidgets.QWidget(self) + + main_layout.addWidget(body_widget) + + content_widget = QtWidgets.QWidget(body_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 0, 3) + + self.content_widget = content_widget + self.content_layout = content_layout + + if not expandable: + label_widget = QtWidgets.QLabel( + input_data["label"], parent=body_widget + ) + label_widget.setObjectName("DictLabel") + + body_layout = QtWidgets.QVBoxLayout(body_widget) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(5) + body_layout.addWidget(label_widget) + body_layout.addWidget(content_widget) + + self.label_widget = label_widget + + else: + body_widget.set_content_widget(content_widget) + self.label_widget = body_widget.label_widget + expanded = input_data.get("expanded", False) + if expanded: + self.toggle_content() + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) From cedb8dd2d78650806058f09a5b9c8db51ebc15aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 10:11:22 +0200 Subject: [PATCH 641/947] added expandable false to current dict types --- .../projects_schema/1_plugins_gui_schema.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index bed839e6dc..4b7b19a74d 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -16,6 +16,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "ExtractCelactionDeadline", "label": "ExtractCelactionDeadline", "is_group": true, @@ -79,6 +80,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "IntegrateFtrackNote", "label": "IntegrateFtrackNote", "is_group": true, @@ -118,6 +120,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "IntegrateMasterVersion", "label": "IntegrateMasterVersion", "is_group": true, @@ -130,6 +133,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractJpegEXR", "label": "ExtractJpegEXR", "is_group": true, @@ -156,6 +160,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractReview", "label": "ExtractReview", "is_group": true, @@ -174,6 +179,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractBurnin", "label": "ExtractBurnin", "is_group": true, @@ -227,6 +233,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "IntegrateAssetNew", "label": "IntegrateAssetNew", "is_group": true, @@ -239,6 +246,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ProcessSubmittedJobOnFarm", "label": "ProcessSubmittedJobOnFarm", "is_group": true, @@ -278,6 +286,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "ValidateModelName", "label": "Validate Model Name", "is_group": true, @@ -299,6 +308,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ValidateAssemblyName", "label": "Validate Assembly Name", "is_group": true, @@ -311,6 +321,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ValidateShaderName", "label": "ValidateShaderName", "is_group": true, @@ -328,6 +339,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ValidateMeshHasOverlappingUVs", "label": "ValidateMeshHasOverlappingUVs", "is_group": true, @@ -360,6 +372,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "CreateWriteRender", "label": "CreateWriteRender", "is_group": true, @@ -373,6 +386,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "CreateWritePrerender", "label": "CreateWritePrerender", "is_group": true, @@ -394,6 +408,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "ExtractThumbnail", "label": "ExtractThumbnail", "is_group": true, @@ -411,6 +426,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ValidateNukeWriteKnobs", "label": "ValidateNukeWriteKnobs", "is_group": true, @@ -428,6 +444,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractReviewDataLut", "label": "ExtractReviewDataLut", "is_group": true, @@ -441,6 +458,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractReviewDataMov", "label": "ExtractReviewDataMov", "is_group": true, @@ -459,6 +477,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractSlateFrame", "label": "ExtractSlateFrame", "is_group": true, @@ -472,6 +491,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "NukeSubmitDeadline", "label": "NukeSubmitDeadline", "is_group": true, @@ -520,6 +540,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "CollectInstanceVersion", "label": "Collect Instance Version", "is_group": true, @@ -533,6 +554,7 @@ ] }, { "type": "dict", + "expandable": false, "key": "ExtractReviewCutUpVideo", "label": "Extract Review Cut Up Video", "is_group": true, @@ -567,6 +589,7 @@ "children": [ { "type": "dict", + "expandable": false, "key": "CreateShotClip", "label": "Create Shot Clip", "is_group": true, From 32ac3a77d7afd073e26b1b1fac94f6444c8754c6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 10:16:23 +0200 Subject: [PATCH 642/947] extended modified attribute --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8e66a5b1c2..4a23b93a85 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2049,7 +2049,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): @property def is_modified(self): if self.is_group: - return self.child_modified + return self._is_modified or self.child_modified return False @property From 00b5ac261a6379f529de684de040c79858416287 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 10:19:49 +0200 Subject: [PATCH 643/947] removed dict-expanding input type as is duplicated --- .../1_ftrack_projects_gui_schema.json | 9 +- .../projects_schema/1_plugins_gui_schema.json | 57 +++-- .../1_applications_gui_schema.json | 3 +- .../studio_schema/1_intents_gui_schema.json | 3 +- .../studio_schema/1_tools_gui_schema.json | 3 +- .../studio_schema/1_tray_items.json | 13 +- .../config_setting/widgets/inputs.py | 226 ------------------ 7 files changed, 58 insertions(+), 256 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json index ac696d18d0..51c05aeb3a 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json @@ -1,11 +1,13 @@ { "key": "ftrack", - "type": "dict-expanding", + "type": "dict", + "expandable": true, "label": "Ftrack", "children": [ { "key": "status_update", - "type": "dict-expanding", + "type": "dict", + "expandable": true, "label": "Status updates", "is_group": true, "is_file": true, @@ -22,7 +24,8 @@ ] }, { "key": "status_version_to_task", - "type": "dict-expanding", + "type": "dict", + "expandable": true, "label": "Version status to Task status", "is_group": true, "is_file": true, diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 4b7b19a74d..bb323c9803 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -1,15 +1,18 @@ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "plugins", "label": "Plugins", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "celaction", "label": "CelAction", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, @@ -68,12 +71,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "ftrack", "label": "Ftrack", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, @@ -108,12 +113,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "global", "label": "Global", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, @@ -189,7 +196,8 @@ "key": "enabled", "label": "Enabled" }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "options", "label": "Burnin formating options", "children": [ @@ -274,12 +282,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "maya", "label": "Maya", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, @@ -360,12 +370,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "nuke", "label": "Nuke", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "create", "label": "Create plugins", "is_file": true, @@ -401,7 +413,8 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, @@ -528,12 +541,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "nukestudio", "label": "NukeStudio", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, @@ -577,12 +592,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "resolve", "label": "DaVinci Resolve", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "create", "label": "Creator plugins", "is_file": true, @@ -617,12 +634,14 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "standalonepublisher", "label": "StandalonePublisher", "children": [ { - "type": "dict-expanding", + "type": "dict", + "expandable": true, "key": "publish", "label": "Publish plugins", "is_file": true, diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json index 2e60ed360d..bc2c9f239c 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json @@ -1,7 +1,8 @@ { "key": "applications", - "type": "dict-expanding", + "type": "dict", "label": "Applications", + "expandable": true, "is_file": true, "is_group": true, "children": [ diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json index 37d525cfb6..18f3d42ba7 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json @@ -1,7 +1,8 @@ { "key": "intent", - "type": "dict-expanding", + "type": "dict", "label": "Intent Setting", + "expandable": true, "is_group": true, "is_file": true, "children": [ diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json index 4c905a3826..bf35326d1c 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json @@ -1,7 +1,8 @@ { "key": "tools", - "type": "dict-expanding", + "type": "dict", "label": "Tools", + "expandable": true, "is_group": true, "is_file": true, "children": [ diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index 849019c89e..7cea724f29 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -1,7 +1,8 @@ { "key": "tray_modules", - "type": "dict-expanding", + "type": "dict", "label": "Modules", + "expandable": true, "is_file": true, "is_group": true, "children": [ @@ -65,10 +66,10 @@ "type": "dict-invisible", "children": [ { - "type": "dict-expanding", + "type": "dict", "key": "Rest Api", "label": "Rest Api", - "MISINGKEYCONF": {"exclude_ports": []}, + "expandable": true, "children": [ { "type": "int", @@ -88,9 +89,10 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", "key": "Timers Manager", "label": "Timers Manager", + "expandable": true, "children": [ { "type": "float", @@ -103,9 +105,10 @@ } ] }, { - "type": "dict-expanding", + "type": "dict", "key": "Clockify", "label": "Clockify", + "expandable": true, "children": [ { "type": "text-singleline", diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4a23b93a85..f0feb6fce3 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1639,231 +1639,6 @@ class ModifiableDict(ExpandingWidget, InputObject): # Dictionaries -class DictExpandWidget(ExpandingWidget, ConfigObject): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - if values is AS_WIDGET: - raise TypeError("Can't use \"{}\" as widget item.".format( - self.__class__.__name__ - )) - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self.any_parent_is_group = any_parent_is_group - - self._is_group = input_data.get("is_group", False) - - self._state = None - self._child_state = None - - super(DictExpandWidget, self).__init__(input_data["label"], parent) - - content_widget = QtWidgets.QWidget(self) - content_widget.setVisible(False) - - content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 0, 3) - - self.set_content_widget(content_widget) - - expanded = input_data.get("expanded", False) - if expanded: - self.toggle_content() - - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - self.content_widget = content_widget - self.content_layout = content_layout - - self.input_fields = [] - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) - - def remove_overrides(self): - self._is_overriden = False - self._is_modified = False - self._was_overriden = False - for item in self.input_fields: - item.remove_overrides() - - def discard_changes(self): - for item in self.input_fields: - item.discard_changes() - - self._is_modified = self.child_modified - self._is_overriden = self._was_overriden - - def set_as_overriden(self): - if self.is_overriden: - return - - if self.is_group: - self._is_overriden = True - return - - for item in self.input_fields: - item.set_as_overriden() - - def update_global_values(self, values): - for item in self.input_fields: - item.update_global_values(values) - - def apply_overrides(self, parent_values): - # Make sure this is set to False - self._state = None - self._child_state = None - - metadata = {} - groups = tuple() - override_values = NOT_SET - if parent_values is not NOT_SET: - metadata = parent_values.get(METADATA_KEY) or metadata - groups = metadata.get("groups") or groups - override_values = parent_values.get(self.key, override_values) - - self._is_overriden = self.key in groups - - for item in self.input_fields: - item.apply_overrides(override_values) - - if not self._is_overriden: - self._is_overriden = ( - self.is_group - and self.is_overidable - and self.child_overriden - ) - self._was_overriden = bool(self._is_overriden) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_group: - if self.is_overidable: - self._is_overriden = True - - # TODO update items - if item is not None: - for _item in self.input_fields: - if _item is not item: - _item.update_style() - - self.value_changed.emit(self) - - self.update_style() - - def hierarchical_style_update(self): - self.update_style() - for input_field in self.input_fields: - input_field.hierarchical_style_update() - - def update_style(self, is_overriden=None): - child_modified = self.child_modified - child_state = self.style_state( - self.child_invalid, self.child_overriden, child_modified - ) - if child_state: - child_state = "child-{}".format(child_state) - - if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) - self._child_state = child_state - - state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return - - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - self._state = state - - @property - def is_modified(self): - if self.is_group: - return self.child_modified - return False - - @property - def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False - - @property - def child_overriden(self): - for input_field in self.input_fields: - if input_field.is_overriden or input_field.child_overriden: - return True - return False - - @property - def child_invalid(self): - for input_field in self.input_fields: - if input_field.child_invalid: - return True - return False - - def get_invalid(self): - output = [] - for input_field in self.input_fields: - output.extend(input_field.get_invalid()) - return output - - def item_value(self): - output = {} - for input_field in self.input_fields: - # TODO maybe merge instead of update should be used - # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) - return output - - def add_children_gui(self, child_configuration, values): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - item = klass( - child_configuration, values, self.keys, self - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addWidget(item) - - self.input_fields.append(item) - return item - - def overrides(self): - if not self.is_overriden and not self.child_overriden: - return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - values[METADATA_KEY] = {"groups": groups} - return {self.key: values}, self.is_group - - class DictWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) @@ -2467,7 +2242,6 @@ TypeToKlass.types["int"] = IntegerWidget TypeToKlass.types["float"] = FloatWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict TypeToKlass.types["dict"] = DictWidget -TypeToKlass.types["dict-expanding"] = DictExpandWidget TypeToKlass.types["dict-form"] = DictFormWidget TypeToKlass.types["dict-invisible"] = DictInvisible TypeToKlass.types["list"] = ListWidget From 8f4a7138e5104cb6b89e03678ad321e121df5b12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:26:44 +0200 Subject: [PATCH 644/947] form widget has update_style method --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f0feb6fce3..dd1af3eb9d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2127,6 +2127,10 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def update_style(self): + for item in self.input_fields: + item.update_style() + def remove_overrides(self): self._is_overriden = False self._is_modified = False From c954fafeb0e4a9fbaafe2c416cd7971a1dd1ac6c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:29:10 +0200 Subject: [PATCH 645/947] trigger hierarchical_style_update instead of triggering update_style for each item --- pype/tools/config_setting/config_setting/widgets/inputs.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index dd1af3eb9d..9896fda4e1 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1986,12 +1986,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): if self.is_group: if self.is_overidable: self._is_overriden = True - # TODO update items - if item is not None: - is_overriden = self.is_overriden - for _item in self.input_fields: - if _item is not item: - _item.update_style(is_overriden) + self.hierarchical_style_update() self.value_changed.emit(self) From 5393b79d987c81bd51f4e3c3d8c4348b4546b062 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:31:24 +0200 Subject: [PATCH 646/947] label widget in dictionary widget is wrapped with QWidget --- .../config_setting/widgets/inputs.py | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9896fda4e1..f6fc0310aa 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1692,26 +1692,33 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.content_widget = content_widget self.content_layout = content_layout - if not expandable: - label_widget = QtWidgets.QLabel( - input_data["label"], parent=body_widget - ) - label_widget.setObjectName("DictLabel") - - body_layout = QtWidgets.QVBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(5) - body_layout.addWidget(label_widget) - body_layout.addWidget(content_widget) - - self.label_widget = label_widget - - else: + if expandable: body_widget.set_content_widget(content_widget) self.label_widget = body_widget.label_widget expanded = input_data.get("expanded", False) if expanded: - self.toggle_content() + body_widget.toggle_content() + + else: + top_widget = QtWidgets.QWidget(body_widget) + top_layout = QtWidgets.QHBoxLayout(top_widget) + top_layout.setContentsMargins(0, 0, 0, 0) + top_layout.setSpacing(5) + + label_widget = QtWidgets.QLabel( + input_data["label"], parent=top_widget + ) + label_widget.setObjectName("DictLabel") + + top_layout.addWidget(label_widget) + + body_layout = QtWidgets.QVBoxLayout(body_widget) + body_layout.setContentsMargins(0, 0, 0, 0) + body_layout.setSpacing(5) + body_layout.addWidget(top_widget) + body_layout.addWidget(content_widget) + + self.label_widget = label_widget self.setAttribute(QtCore.Qt.WA_StyledBackground) From f994d56464fa2f0fe6e104d385a22e5c266554e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:35:58 +0200 Subject: [PATCH 647/947] it is possible to add checkbox next to label instead of child input --- .../config_setting/widgets/inputs.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f6fc0310aa..56ce22bdaf 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1722,6 +1722,31 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.setAttribute(QtCore.Qt.WA_StyledBackground) + checkbox_widget = None + checkbox_key = input_data.get("checkbox_key") + if checkbox_key: + checkbox_input_data = { + "key": checkbox_key, + "label": "test" + } + if expandable: + checkbox_widget = BooleanWidget( + checkbox_input_data, values, self.keys, self, + label_widget=self.label_widget, + # parent_widget=body_widget.top_part + ) + body_widget.top_part.layout().addWidget(checkbox_widget) + else: + checkbox_widget = BooleanWidget( + checkbox_input_data, values, self.keys, self, + label_widget=self.label_widget, + # parent_widget=top_widget + ) + top_layout.addWidget(checkbox_widget) + + self.input_fields.append(checkbox_widget) + checkbox_widget.value_changed.connect(self._on_value_change) + for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) @@ -2129,10 +2154,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified self._is_overriden = self._was_overriden - def update_style(self): - for item in self.input_fields: - item.update_style() - def remove_overrides(self): self._is_overriden = False self._is_modified = False From 354b5b26f4c81bde9185c247364d87847ca0d51b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:44:51 +0200 Subject: [PATCH 648/947] added validation for checkbox_key --- .../config_setting/config_setting/widgets/lib.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index fe4e514aaf..ac18f09669 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -219,20 +219,24 @@ def validate_is_group_is_unique_in_hierarchy( def validate_keys_are_unique(schema_data, keys=None): + children = schema_data.get("children") + if not children: + return + is_top = keys is None if keys is None: keys = [schema_data["key"]] else: keys.append(schema_data["key"]) - children = schema_data.get("children") - if not children: - return - child_queue = Queue() for child in children: child_queue.put(child) + checkbox_key = schema_data.get("checkbox_key") + if checkbox_key: + child_queue.put({"key": checkbox_key}) + child_inputs = [] while not child_queue.empty(): child = child_queue.get() From 2960dcb1e0808b7623369bd49602fc4e93d467c3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:45:09 +0200 Subject: [PATCH 649/947] expandable widget has ability to hide toolbox button --- .../config_setting/config_setting/widgets/widgets.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index e551d1e9c3..d90242600e 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -53,6 +53,8 @@ class ExpandingWidget(QtWidgets.QWidget): super(ExpandingWidget, self).__init__(parent) self.setObjectName("ExpandingWidget") + self.toolbox_hidden = False + top_part = ClickableWidget(parent=self) button_size = QtCore.QSize(5, 5) @@ -82,6 +84,12 @@ class ExpandingWidget(QtWidgets.QWidget): self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) + def hide_toolbox(self): + self.button_toggle.setArrowType(QtCore.Qt.NoArrow) + self.toolbox_hidden = True + self.content_widget.setVisible(False) + self.parent().updateGeometry() + def set_content_widget(self, content_widget): main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(9, 9, 0, 9) @@ -98,6 +106,8 @@ class ExpandingWidget(QtWidgets.QWidget): self.toggle_content(not self.button_toggle.isChecked()) def toggle_content(self, *args): + if self.toolbox_hidden: + return if len(args) > 0: checked = args[0] else: From 977855566b51f484890955c1e520eaee1f9a1d15 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:45:34 +0200 Subject: [PATCH 650/947] checkbox creation is same --- .../config_setting/widgets/inputs.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 56ce22bdaf..477fdc9292 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1726,22 +1726,15 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): checkbox_key = input_data.get("checkbox_key") if checkbox_key: checkbox_input_data = { - "key": checkbox_key, - "label": "test" + "key": checkbox_key } + checkbox_widget = BooleanWidget( + checkbox_input_data, values, self.keys, self, + label_widget=self.label_widget + ) if expandable: - checkbox_widget = BooleanWidget( - checkbox_input_data, values, self.keys, self, - label_widget=self.label_widget, - # parent_widget=body_widget.top_part - ) body_widget.top_part.layout().addWidget(checkbox_widget) else: - checkbox_widget = BooleanWidget( - checkbox_input_data, values, self.keys, self, - label_widget=self.label_widget, - # parent_widget=top_widget - ) top_layout.addWidget(checkbox_widget) self.input_fields.append(checkbox_widget) From 7b679f5e79239565875b490c7d89208ed4d2e7f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:45:56 +0200 Subject: [PATCH 651/947] trigger hierarchical_style_update instead of individual update_style on items --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 477fdc9292..3c5c246c43 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1806,11 +1806,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): if self.is_overidable: self._is_overriden = True - # TODO update items - if item is not None: - for _item in self.input_fields: - if _item is not item: - _item.update_style() + self.hierarchical_style_update() self.value_changed.emit(self) From 3800fc91dc3e9b796ff95e60a4f232b6c017a13d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 11:46:19 +0200 Subject: [PATCH 652/947] hide content and arrow if only checkbox key is set in dict widget --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3c5c246c43..64d2cbb761 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1740,7 +1740,11 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.input_fields.append(checkbox_widget) checkbox_widget.value_changed.connect(self._on_value_change) - for child_data in input_data.get("children", []): + children_data = input_data.get("children", []) + if expandable and checkbox_widget and not children_data: + body_widget.hide_toolbox() + + for child_data in children_data: self.add_children_gui(child_data, values) def remove_overrides(self): From 5e3599b8d08272923bac24f5f1bf623c50563249 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 12:33:57 +0200 Subject: [PATCH 653/947] expandign widget can set custom context margins --- pype/tools/config_setting/config_setting/widgets/widgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index d90242600e..ba6a3c6629 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -90,9 +90,11 @@ class ExpandingWidget(QtWidgets.QWidget): self.content_widget.setVisible(False) self.parent().updateGeometry() - def set_content_widget(self, content_widget): + def set_content_widget(self, content_widget, margins=None): main_layout = QtWidgets.QVBoxLayout(self) - main_layout.setContentsMargins(9, 9, 0, 9) + if margins is None: + margins = (9, 9, 0, 9) + main_layout.setContentsMargins(*margins) content_widget.setVisible(False) From bbea0da42c57fe6c488e7e5f25e617068d995c4c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 12:34:21 +0200 Subject: [PATCH 654/947] ExpandingWidget style is not used anymore --- .../config_setting/config_setting/style/style.css | 12 ++++++------ .../config_setting/config_setting/widgets/widgets.py | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 996b73b9cd..937259ba7b 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -124,7 +124,7 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } -#ExpandingWidget, #ModifiableDict, #DictWidget { +#ModifiableDict, #DictWidget { border-style: solid; border-color: #455c6e; border-left-width: 3px; @@ -132,22 +132,22 @@ QPushButton[btn-type="expand-toggle"] { border-right-width: 0px; border-top-width: 0px; } -#ExpandingWidget:hover, #ModifiableDict:hover, #DictWidget:hover { +#ModifiableDict:hover, #DictWidget:hover { border-color: #62839d; } -#ExpandingWidget[state="child-modified"], #ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { +#ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { border-color: #106aa2; } -#ExpandingWidget[state="child-modified"]:hover, #ModifiableDict[state="child-modified"]:hover, #DictWidget[state="child-modified"]:hover { +#ModifiableDict[state="child-modified"]:hover, #DictWidget[state="child-modified"]:hover { border-color: #137cbd; } -#ExpandingWidget[state="child-invalid"], #ModifiableDict[state="child-invalid"], #DictWidget[state="child-invalid"] { +#ModifiableDict[state="child-invalid"], #DictWidget[state="child-invalid"] { border-color: #ad2e2e; } -#ExpandingWidget[state="child-invalid"]:hover, #ModifiableDict[state="child-invalid"]:hover, #DictWidget[state="child-invalid"]:hover { +#ModifiableDict[state="child-invalid"]:hover, #DictWidget[state="child-invalid"]:hover { border-color: #c93636; } diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index ba6a3c6629..c0045e37fd 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -51,7 +51,6 @@ class ClickableWidget(QtWidgets.QLabel): class ExpandingWidget(QtWidgets.QWidget): def __init__(self, label, parent): super(ExpandingWidget, self).__init__(parent) - self.setObjectName("ExpandingWidget") self.toolbox_hidden = False From 9bd6f5961c315a17148383164ab8b23fdddd84c5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 12:34:46 +0200 Subject: [PATCH 655/947] expanding widget can hide arrow and keep context showed --- pype/tools/config_setting/config_setting/widgets/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index c0045e37fd..db41fda1f6 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -83,10 +83,10 @@ class ExpandingWidget(QtWidgets.QWidget): self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) - def hide_toolbox(self): + def hide_toolbox(self, hide_content=False): self.button_toggle.setArrowType(QtCore.Qt.NoArrow) self.toolbox_hidden = True - self.content_widget.setVisible(False) + self.content_widget.setVisible(not hide_content) self.parent().updateGeometry() def set_content_widget(self, content_widget, margins=None): From 5c806ca77e7254896a5c43616bdde20f693def77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 12:35:15 +0200 Subject: [PATCH 656/947] content margins are same for both cases --- pype/tools/config_setting/config_setting/widgets/inputs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 64d2cbb761..2fb83566b0 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1673,14 +1673,13 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.keys = keys main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(5, 5, 0, 5) main_layout.setSpacing(0) expandable = input_data.get("expandable", True) if expandable: - main_layout.setContentsMargins(0, 0, 0, 0) body_widget = ExpandingWidget(input_data["label"], self) else: - main_layout.setContentsMargins(5, 5, 0, 5) body_widget = QtWidgets.QWidget(self) main_layout.addWidget(body_widget) From fd58aeb7fdd5a68018c171fae3c44e20ae396c74 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 12:35:42 +0200 Subject: [PATCH 657/947] as body widget is always used expanding widget in dict widget --- .../config_setting/widgets/inputs.py | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 2fb83566b0..3737d8c858 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1676,11 +1676,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): main_layout.setContentsMargins(5, 5, 0, 5) main_layout.setSpacing(0) - expandable = input_data.get("expandable", True) - if expandable: - body_widget = ExpandingWidget(input_data["label"], self) - else: - body_widget = QtWidgets.QWidget(self) + body_widget = ExpandingWidget(input_data["label"], self) main_layout.addWidget(body_widget) @@ -1691,33 +1687,16 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.content_widget = content_widget self.content_layout = content_layout + body_widget.set_content_widget(content_widget, (4, 4, 0, 4)) + self.label_widget = body_widget.label_widget + + expandable = input_data.get("expandable", True) if expandable: - body_widget.set_content_widget(content_widget) - self.label_widget = body_widget.label_widget expanded = input_data.get("expanded", False) if expanded: body_widget.toggle_content() - else: - top_widget = QtWidgets.QWidget(body_widget) - top_layout = QtWidgets.QHBoxLayout(top_widget) - top_layout.setContentsMargins(0, 0, 0, 0) - top_layout.setSpacing(5) - - label_widget = QtWidgets.QLabel( - input_data["label"], parent=top_widget - ) - label_widget.setObjectName("DictLabel") - - top_layout.addWidget(label_widget) - - body_layout = QtWidgets.QVBoxLayout(body_widget) - body_layout.setContentsMargins(0, 0, 0, 0) - body_layout.setSpacing(5) - body_layout.addWidget(top_widget) - body_layout.addWidget(content_widget) - - self.label_widget = label_widget + body_widget.hide_toolbox(hide_content=False) self.setAttribute(QtCore.Qt.WA_StyledBackground) @@ -1741,7 +1720,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): children_data = input_data.get("children", []) if expandable and checkbox_widget and not children_data: - body_widget.hide_toolbox() + body_widget.hide_toolbox(hide_content=True) for child_data in children_data: self.add_children_gui(child_data, values) From 01ae1ef294226fce524b6045d660400dcb8f5de1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 12:55:26 +0200 Subject: [PATCH 658/947] checkbox key requires to have widget specification in children --- .../config_setting/widgets/inputs.py | 59 ++++++++++--------- .../config_setting/widgets/lib.py | 4 -- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3737d8c858..cf1e7b017e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1684,47 +1684,33 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 0, 3) + body_widget.set_content_widget(content_widget, (4, 4, 0, 4)) + + self.body_widget = body_widget self.content_widget = content_widget self.content_layout = content_layout - body_widget.set_content_widget(content_widget, (4, 4, 0, 4)) self.label_widget = body_widget.label_widget + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.checkbox_widget = None + self.checkbox_key = input_data.get("checkbox_key") + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + expandable = input_data.get("expandable", True) - if expandable: + if len(self.input_fields) == 1 and self.checkbox_widget: + body_widget.hide_toolbox(hide_content=True) + + elif expandable: expanded = input_data.get("expanded", False) if expanded: body_widget.toggle_content() else: body_widget.hide_toolbox(hide_content=False) - self.setAttribute(QtCore.Qt.WA_StyledBackground) - - checkbox_widget = None - checkbox_key = input_data.get("checkbox_key") - if checkbox_key: - checkbox_input_data = { - "key": checkbox_key - } - checkbox_widget = BooleanWidget( - checkbox_input_data, values, self.keys, self, - label_widget=self.label_widget - ) - if expandable: - body_widget.top_part.layout().addWidget(checkbox_widget) - else: - top_layout.addWidget(checkbox_widget) - - self.input_fields.append(checkbox_widget) - checkbox_widget.value_changed.connect(self._on_value_change) - - children_data = input_data.get("children", []) - if expandable and checkbox_widget and not children_data: - body_widget.hide_toolbox(hide_content=True) - - for child_data in children_data: - self.add_children_gui(child_data, values) - def remove_overrides(self): self._is_overriden = False self._is_modified = False @@ -1868,6 +1854,10 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) + if self.checkbox_key and not self.checkbox_widget: + key = child_configuration.get("key") + if key == self.checkbox_key: + return self._add_checkbox_child(child_configuration, values) item = klass( child_configuration, values, self.keys, self @@ -1878,6 +1868,17 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.input_fields.append(item) return item + def _add_checkbox_child(self, child_configuration, values): + item = BooleanWidget( + child_configuration, values, self.keys, self, self.label_widget + ) + item.value_changed.connect(self._on_value_change) + + self.body_widget.top_part.layout().addWidget(item) + self.checkbox_widget = item + self.input_fields.append(item) + return item + def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index ac18f09669..c416f7a5b0 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -233,10 +233,6 @@ def validate_keys_are_unique(schema_data, keys=None): for child in children: child_queue.put(child) - checkbox_key = schema_data.get("checkbox_key") - if checkbox_key: - child_queue.put({"key": checkbox_key}) - child_inputs = [] while not child_queue.empty(): child = child_queue.get() From aae3fbf971afbcf96956aea5903badbee10e7a79 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 13:01:03 +0200 Subject: [PATCH 659/947] modified few schemas to match new dict checkbox key --- .../projects_schema/1_plugins_gui_schema.json | 69 ++++++++++++------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index bb323c9803..19d5127a95 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -19,20 +19,21 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ExtractCelactionDeadline", "label": "ExtractCelactionDeadline", "is_group": true, "children": [ { + "type": "boolean", + "key": "enabled", + "label": "Enabled", + "default": true + }, { "type": "dict-form", "children": [ { - "type": "boolean", - "key": "enabled", - "label": "Enabled", - "default": true - }, { "type": "text-singleline", "key": "deadline_department", "label": "Deadline apartment", @@ -85,7 +86,8 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "IntegrateFtrackNote", "label": "IntegrateFtrackNote", "is_group": true, @@ -127,7 +129,8 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "IntegrateMasterVersion", "label": "IntegrateMasterVersion", "is_group": true, @@ -140,7 +143,8 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ExtractJpegEXR", "label": "ExtractJpegEXR", "is_group": true, @@ -167,9 +171,10 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ExtractReview", "label": "ExtractReview", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -186,9 +191,10 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ExtractBurnin", "label": "ExtractBurnin", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -241,7 +247,7 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "IntegrateAssetNew", "label": "IntegrateAssetNew", "is_group": true, @@ -254,9 +260,10 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ProcessSubmittedJobOnFarm", "label": "ProcessSubmittedJobOnFarm", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -296,9 +303,10 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, "key": "ValidateModelName", "label": "Validate Model Name", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -318,9 +326,10 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ValidateAssemblyName", "label": "Validate Assembly Name", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -331,9 +340,10 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ValidateShaderName", "label": "ValidateShaderName", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -349,9 +359,10 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ValidateMeshHasOverlappingUVs", "label": "ValidateMeshHasOverlappingUVs", + "checkbox_key": "enabled", "is_group": true, "children": [ { @@ -421,7 +432,8 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ExtractThumbnail", "label": "ExtractThumbnail", "is_group": true, @@ -439,7 +451,8 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ValidateNukeWriteKnobs", "label": "ValidateNukeWriteKnobs", "is_group": true, @@ -457,7 +470,8 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ExtractReviewDataLut", "label": "ExtractReviewDataLut", "is_group": true, @@ -471,7 +485,8 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ExtractReviewDataMov", "label": "ExtractReviewDataMov", "is_group": true, @@ -490,7 +505,7 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "ExtractSlateFrame", "label": "ExtractSlateFrame", "is_group": true, @@ -504,7 +519,7 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, "key": "NukeSubmitDeadline", "label": "NukeSubmitDeadline", "is_group": true, @@ -555,7 +570,8 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "CollectInstanceVersion", "label": "Collect Instance Version", "is_group": true, @@ -569,7 +585,8 @@ ] }, { "type": "dict", - "expandable": false, + "expandable": true, + "checkbox_key": "enabled", "key": "ExtractReviewCutUpVideo", "label": "Extract Review Cut Up Video", "is_group": true, @@ -606,7 +623,7 @@ "children": [ { "type": "dict", - "expandable": false, + "expandable": true, "key": "CreateShotClip", "label": "Create Shot Clip", "is_group": true, From ce6e64e5734dcde2a7c0f0b5282f7e1f9532c2e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 14:45:09 +0200 Subject: [PATCH 660/947] adde is_nullable to inputs --- .../config_setting/widgets/inputs.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index cf1e7b017e..f1eee2b33b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -19,6 +19,7 @@ class ConfigObject: _was_overriden = False _is_invalid = False _is_group = False + _is_nullable = False _log = None @@ -53,6 +54,10 @@ class ConfigObject: """Value set in is not valid.""" return self._is_group + @property + def is_nullable(self): + return self._is_nullable + @property def is_overidable(self): """Should care about overrides.""" @@ -335,6 +340,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self._as_widget = values is AS_WIDGET self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -450,6 +456,7 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): self._as_widget = values is AS_WIDGET self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -561,6 +568,7 @@ class FloatWidget(QtWidgets.QWidget, InputObject): self._as_widget = values is AS_WIDGET self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -681,6 +689,7 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): self._as_widget = values is AS_WIDGET self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -786,6 +795,7 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputObject): self._as_widget = values is AS_WIDGET self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -949,6 +959,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) self._state = None @@ -1142,6 +1153,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self._state = None self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) @@ -1464,6 +1476,7 @@ class ModifiableDict(ExpandingWidget, InputObject): self.any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) inputs_widget = QtWidgets.QWidget(self) inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) @@ -1664,6 +1677,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) self.input_fields = [] From a57fbd8f407cc115bb64674173116440378a9fe6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 1 Sep 2020 15:02:53 +0200 Subject: [PATCH 661/947] feat(nuke): adding image loader --- pype/plugins/nuke/load/load_image.py | 233 ++++++++++++++++++++++++ pype/plugins/nuke/load/load_sequence.py | 18 +- 2 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 pype/plugins/nuke/load/load_image.py diff --git a/pype/plugins/nuke/load/load_image.py b/pype/plugins/nuke/load/load_image.py new file mode 100644 index 0000000000..377d52aa14 --- /dev/null +++ b/pype/plugins/nuke/load/load_image.py @@ -0,0 +1,233 @@ +import re +import nuke + +from avalon.vendor import qargparse +from avalon import api, io + +from pype.hosts.nuke import presets + + +class LoadImage(api.Loader): + """Load still image into Nuke""" + + families = [ + "render2d", "source", "plate", + "render", "prerender", "review", + "image" + ] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd"] + + label = "Load Image" + order = -10 + icon = "image" + color = "white" + + options = [ + qargparse.Integer( + "frame_number", + label="Frame Number", + default=int(nuke.root()["first_frame"].getValue()), + min=1, + max=999999, + help="What frame is reading from?" + ) + ] + + def load(self, context, name, namespace, options): + from avalon.nuke import ( + containerise, + viewer_update_and_undo_stop + ) + self.log.info("__ options: `{}`".format(options)) + frame_number = options.get("frame_number", 1) + + version = context['version'] + version_data = version.get("data", {}) + repr_id = context["representation"]["_id"] + + self.log.info("version_data: {}\n".format(version_data)) + self.log.debug( + "Representation id `{}` ".format(repr_id)) + + last = first = int(frame_number) + + # Fallback to asset name when namespace is None + if namespace is None: + namespace = context['asset']['name'] + + file = self.fname + + if not file: + repr_id = context["representation"]["_id"] + self.log.warning( + "Representation id `{}` is failing to load".format(repr_id)) + return + + file = file.replace("\\", "/") + + repr_cont = context["representation"]["context"] + frame = repr_cont.get("frame") + if frame: + padding = len(frame) + file = file.replace( + frame, + format(frame_number, "0{}".format(padding))) + + read_name = "Read_{0}_{1}_{2}".format( + repr_cont["asset"], + repr_cont["subset"], + repr_cont["representation"]) + + # Create the Loader with the filename path set + with viewer_update_and_undo_stop(): + r = nuke.createNode( + "Read", + "name {}".format(read_name)) + r["file"].setValue(file) + + # Set colorspace defined in version data + colorspace = context["version"]["data"].get("colorspace") + if colorspace: + r["colorspace"].setValue(str(colorspace)) + + # load nuke presets for Read's colorspace + read_clrs_presets = presets.get_colorspace_preset().get( + "nuke", {}).get("read", {}) + + # check if any colorspace presets for read is mathing + preset_clrsp = next((read_clrs_presets[k] + for k in read_clrs_presets + if bool(re.search(k, file))), + None) + if preset_clrsp is not None: + r["colorspace"].setValue(str(preset_clrsp)) + + r["origfirst"].setValue(first) + r["first"].setValue(first) + r["origlast"].setValue(last) + r["last"].setValue(last) + + # add additional metadata from the version to imprint Avalon knob + add_keys = ["source", "colorspace", "author", "fps", "version"] + + data_imprint = { + "frameStart": first, + "frameEnd": last + } + for k in add_keys: + if k == 'version': + data_imprint.update({k: context["version"]['name']}) + else: + data_imprint.update( + {k: context["version"]['data'].get(k, str(None))}) + + data_imprint.update({"objectName": read_name}) + + r["tile_color"].setValue(int("0x4ecd25ff", 16)) + + return containerise(r, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """Update the Loader's path + + Nuke automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + + """ + + from avalon.nuke import ( + update_container + ) + + node = nuke.toNode(container["objectName"]) + frame_number = node["first"].value() + + assert node.Class() == "Read", "Must be Read" + + repr_cont = representation["context"] + + file = api.get_representation_path(representation) + + if not file: + repr_id = representation["_id"] + self.log.warning( + "Representation id `{}` is failing to load".format(repr_id)) + return + + file = file.replace("\\", "/") + + frame = repr_cont.get("frame") + if frame: + padding = len(frame) + file = file.replace( + frame, + format(frame_number, "0{}".format(padding))) + + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + version_data = version.get("data", {}) + + last = first = int(frame_number) + + # Set the global in to the start frame of the sequence + node["origfirst"].setValue(first) + node["first"].setValue(first) + node["origlast"].setValue(last) + node["last"].setValue(last) + + updated_dict = {} + updated_dict.update({ + "representation": str(representation["_id"]), + "frameStart": str(first), + "frameEnd": str(last), + "version": str(version.get("name")), + "colorspace": version_data.get("colorspace"), + "source": version_data.get("source"), + "fps": str(version_data.get("fps")), + "author": version_data.get("author"), + "outputDir": version_data.get("outputDir"), + }) + + # change color of node + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + node["tile_color"].setValue(int("0x4ecd25ff", 16)) + + # Update the imprinted representation + update_container( + node, + updated_dict + ) + self.log.info("udated to version: {}".format(version.get("name"))) + + def remove(self, container): + + from avalon.nuke import viewer_update_and_undo_stop + + node = nuke.toNode(container['objectName']) + assert node.Class() == "Read", "Must be Read" + + with viewer_update_and_undo_stop(): + nuke.delete(node) diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index 601e28c7c1..c5ce288540 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -120,12 +120,12 @@ class LoadSequence(api.Loader): if "#" not in file: frame = repr_cont.get("frame") padding = len(frame) - file = file.replace(frame, "#"*padding) + file = file.replace(frame, "#" * padding) read_name = "Read_{0}_{1}_{2}".format( - repr_cont["asset"], - repr_cont["subset"], - repr_cont["representation"]) + repr_cont["asset"], + repr_cont["subset"], + repr_cont["representation"]) # Create the Loader with the filename path set with viewer_update_and_undo_stop(): @@ -250,7 +250,7 @@ class LoadSequence(api.Loader): if "#" not in file: frame = repr_cont.get("frame") padding = len(frame) - file = file.replace(frame, "#"*padding) + file = file.replace(frame, "#" * padding) # Get start frame from version data version = io.find_one({ @@ -276,10 +276,10 @@ class LoadSequence(api.Loader): last = version_data.get("frameEnd") if first is None: - self.log.warning("Missing start frame for updated version" - "assuming starts at frame 0 for: " - "{} ({})".format( - node['name'].value(), representation)) + self.log.warning( + "Missing start frame for updated version" + "assuming starts at frame 0 for: " + "{} ({})".format(node['name'].value(), representation)) first = 0 first -= self.handle_start From 4970aaf5124cc7e0bfc4d5c1b252e486542da993 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 1 Sep 2020 14:22:30 +0100 Subject: [PATCH 662/947] Remove extra dash --- pype/scripts/otio_burnin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 718943855c..156896a759 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -526,7 +526,7 @@ def burnins_from_data( bit_rate = burnin._streams[0].get("bit_rate") if bit_rate: - ffmpeg_args.append("--b:v {}".format(bit_rate)) + ffmpeg_args.append("-b:v {}".format(bit_rate)) pix_fmt = burnin._streams[0].get("pix_fmt") if pix_fmt: From dabe68f28cd1c391a2b4930af40d60b801bc78e2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 16:50:00 +0200 Subject: [PATCH 663/947] initial commit for anatomy widget --- .../projects_schema/0_project_gui_schema.json | 4 ++ .../config_setting/widgets/__init__.py | 4 +- .../config_setting/widgets/anatomy_inputs.py | 63 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json index 10641d5eda..91bacf2e5a 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json @@ -3,6 +3,10 @@ "type": "dict-invisible", "children": [ { + "type": "anatomy", + "key": "anatomy", + "is_file": true + }, { "type": "schema", "children": [ "1_plugins_gui_schema" diff --git a/pype/tools/config_setting/config_setting/widgets/__init__.py b/pype/tools/config_setting/config_setting/widgets/__init__.py index 0682f00324..034692898c 100644 --- a/pype/tools/config_setting/config_setting/widgets/__init__.py +++ b/pype/tools/config_setting/config_setting/widgets/__init__.py @@ -1,8 +1,10 @@ from .window import MainWidget # TODO properly register inputs to TypeToKlass class from . import inputs +from . import anatomy_inputs __all__ = [ "MainWidget", - "inputs" + "inputs", + "anatomy_inputs" ] diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py new file mode 100644 index 0000000000..addd0d6666 --- /dev/null +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -0,0 +1,63 @@ +import json +import logging +from Qt import QtWidgets, QtCore, QtGui +from .widgets import ( + ExpandingWidget, + ModifiedIntSpinBox, + ModifiedFloatSpinBox +) +from .inputs import ConfigObject, InputObject +from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass + + +class AnatomyWidget(QtWidgets.QWidget, InputObject): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + self._is_group = True + self._state = None + + self.key = "anatomy" + self.start_value = None + + super(AnatomyWidget, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + label = QtWidgets.QLabel("Test") + layout.addWidget(label) + + self.override_value = NOT_SET + + def update_global_values(self, values): + print("* update_global_values") + + def set_value(self, value, *, global_value=False): + print("* set_value") + + def clear_value(self): + print("* clear_value") + + def _on_value_change(self, item=None): + print("* _on_value_change") + + def update_style(self): + print("* update_style") + + def item_value(self): + print("* item_value") + + +class TemplatesWidget: + pass + + + + +TypeToKlass.types["anatomy"] = AnatomyWidget From 13ee08a4ac73fdf4b5e000224da2bbb715b4cc12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Sep 2020 17:55:24 +0200 Subject: [PATCH 664/947] initial commit of path input --- .../config_setting/widgets/inputs.py | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f1eee2b33b..1416bbebda 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2247,8 +2247,291 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): return values, self.is_group +class PathInput(QtWidgets.QLineEdit): + def clear_end_path(self): + value = self.text().strip() + print("clearing") + if value.endswith("/"): + while value and value[-1] == "/": + value = value[:-1] + self.setText(value) + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Backslash: + event.accept() + new_event = QtGui.QKeyEvent( + event.type(), + QtCore.Qt.Key_Slash, + event.modifiers(), + "/", + event.isAutoRepeat(), + event.count() + ) + QtWidgets.QApplication.sendEvent(self, new_event) + return + super(PathInput, self).keyPressEvent(event) + + def focusOutEvent(self, event): + super(PathInput, self).focusOutEvent(event) + self.clear_end_path() + + +class PathWidgetInput(QtWidgets.QWidget, InputObject): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) + self.default_value = input_data.get("default", NOT_SET) + + self._state = None + + super(PathWidgetInput, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.path_input = PathInput(self) + + self.setFocusProxy(self.path_input) + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + layout.addWidget(self.path_input, 1) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + self.update_global_values(values) + + self.override_value = NOT_SET + + self.path_input.textChanged.connect(self._on_value_change) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: + value = self.value_from_values(values) + if value is not NOT_SET: + self.path_input.setText(value) + + elif self.default_value is not NOT_SET: + self.path_input.setText(self.default_value) + + self.global_value = value + self.start_value = self.item_value() + + self._is_modified = self.global_value != self.start_value + + def set_value(self, value, *, global_value=False): + self.path_input.setText(value) + if global_value: + self.start_value = self.item_value() + self.global_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.start_value) + + def clear_value(self): + self.set_value("") + + def focusOutEvent(self, event): + super(PathInput, self).focusOutEvent(event) + value = self.item_value().strip() + if value.endswith("/"): + while value and value[-1] == "/": + value = value[:-1] + self.set_value(value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.global_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.path_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.path_input.text() + + +class PathWidget(QtWidgets.QWidget, InputObject): + value_changed = QtCore.Signal(object) + + platforms = ("windows", "darwin", "linux") + platform_labels_mapping = { + "windows": "Windows", + "darwin": "MacOS", + "linux": "Linux" + } + platform_separators = { + "windows": ";", + "darwin": ":", + "linux": ":" + } + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + super(PathWidget, self).__init__(parent) + + self._parent = parent + self._as_widget = values is AS_WIDGET + + self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) + + self.default_value = input_data.get("default", NOT_SET) + self.multi_platform = input_data.get("multi_platform", NOT_SET) + self.multi_path = input_data.get("multi_path", NOT_SET) + + self.override_value = NOT_SET + + self._state = None + + self.input_fields = [] + + if not self._as_widget: + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + if not self.multi_platform and not self.multi_path: + layout = QtWidgets.QVBoxLayout(self) + else: + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + + self.label_widget = label_widget + + self.content_widget = QtWidgets.QWidget(self) + self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) + + layout.addWidget(self.content_widget) + + self.create_gui() + + self.update_global_values(values) + + def create_gui(self): + if self.multi_platform and self.multi_path: + pass + elif self.multi_platform: + pass + elif self.multi_path: + pass + else: + text_input = QtWidgets.QLineEdit(self.content_widget) + self.setFocusProxy(text_input) + self.content_layout.addWidget(text_input, 1) + self.input_fields.append(text_input) + + def update_global_values(self, values): + value = NOT_SET + if not self._as_widget: + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setText(value) + + elif self.default_value is not NOT_SET: + self.text_input.setText(self.default_value) + + self.global_value = value + self.start_value = self.item_value() + + self._is_modified = self.global_value != self.start_value + + def set_value(self, value, *, global_value=False): + self.text_input.setText(value) + if global_value: + self.start_value = self.item_value() + self.global_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.start_value) + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.global_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.text_input.text() + + TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["text-singleline"] = TextSingleLineWidget +TypeToKlass.types["path-input"] = PathWidgetInput TypeToKlass.types["text-multiline"] = TextMultiLineWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget From 5b74678220750d0da52c9f290bcbc1299fc7106e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 10:50:27 +0200 Subject: [PATCH 665/947] PathWidgetInput cleanup --- .../config_setting/widgets/inputs.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 1416bbebda..24d0259baf 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2250,13 +2250,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): class PathInput(QtWidgets.QLineEdit): def clear_end_path(self): value = self.text().strip() - print("clearing") if value.endswith("/"): while value and value[-1] == "/": value = value[:-1] self.setText(value) def keyPressEvent(self, event): + # Always change backslash `\` for forwardslash `/` if event.key() == QtCore.Qt.Key_Backslash: event.accept() new_event = QtGui.QKeyEvent( @@ -2276,7 +2276,7 @@ class PathInput(QtWidgets.QLineEdit): self.clear_end_path() -class PathWidgetInput(QtWidgets.QWidget, InputObject): +class PathInputWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( @@ -2291,7 +2291,7 @@ class PathWidgetInput(QtWidgets.QWidget, InputObject): self._state = None - super(PathWidgetInput, self).__init__(parent) + super(PathInputWidget, self).__init__(parent) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -2350,12 +2350,8 @@ class PathWidgetInput(QtWidgets.QWidget, InputObject): self.set_value("") def focusOutEvent(self, event): + self.path_input.clear_end_path() super(PathInput, self).focusOutEvent(event) - value = self.item_value().strip() - if value.endswith("/"): - while value and value[-1] == "/": - value = value[:-1] - self.set_value(value) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -2531,7 +2527,7 @@ class PathWidget(QtWidgets.QWidget, InputObject): TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["text-singleline"] = TextSingleLineWidget -TypeToKlass.types["path-input"] = PathWidgetInput +TypeToKlass.types["path-input"] = PathInputWidget TypeToKlass.types["text-multiline"] = TextMultiLineWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget From bf4205f23d8f4467376696a3b2b0263c75b69665 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 11:08:03 +0200 Subject: [PATCH 666/947] path widget is partially completed --- .../config_setting/widgets/inputs.py | 107 ++++++++++++------ 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 24d0259baf..63db5cb2e2 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2413,8 +2413,8 @@ class PathWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self.multi_platform = input_data.get("multi_platform", NOT_SET) - self.multi_path = input_data.get("multi_path", NOT_SET) + self.multiplatform = input_data.get("multiplatform", False) + self.multipath = input_data.get("multipath", False) self.override_value = NOT_SET @@ -2428,10 +2428,10 @@ class PathWidget(QtWidgets.QWidget, InputObject): keys.append(self.key) self.keys = keys - if not self.multi_platform and not self.multi_path: - layout = QtWidgets.QVBoxLayout(self) - else: + if not self.multiplatform and not self.multipath: layout = QtWidgets.QHBoxLayout(self) + else: + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) @@ -2441,30 +2441,69 @@ class PathWidget(QtWidgets.QWidget, InputObject): layout.addWidget(label_widget, 0) self.label_widget = label_widget + layout.addWidget(label_widget) self.content_widget = QtWidgets.QWidget(self) self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) + self.content_layout.setSpacing(0) + self.content_layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.content_widget) - self.create_gui() + self.create_gui(values) self.update_global_values(values) - def create_gui(self): - if self.multi_platform and self.multi_path: - pass - elif self.multi_platform: - pass - elif self.multi_path: - pass - else: - text_input = QtWidgets.QLineEdit(self.content_widget) - self.setFocusProxy(text_input) - self.content_layout.addWidget(text_input, 1) - self.input_fields.append(text_input) + def create_gui(self, values): + if not self.multiplatform and not self.multipath: + input_data = {"key": self.key} + path_input = PathInputWidget( + input_data, values, self.keys, self, self.label_widget + ) + self.setFocusProxy(path_input) + self.content_layout.addWidget(path_input) + self.input_fields.append(path_input) + path_input.value_changed.connect(self._on_value_change) + return + + input_data_for_list = { + "object_type": "path-input" + } + if not self.multiplatform: + input_data_for_list["key"] = self.key + input_widget = ListWidget( + input_data_for_list, values, self.keys, self, self.label_widget + ) + self.setFocusProxy(input_widget) + self.content_layout.addWidget(input_widget) + self.input_fields.append(input_widget) + input_widget.value_changed.connect(self._on_value_change) + return + + proxy_widget = QtWidgets.QWidget(self.content_widget) + proxy_layout = QtWidgets.QFormLayout(proxy_widget) + for platform_key in self.platforms: + platform_label = self.platform_labels_mapping[platform_key] + label_widget = QtWidgets.QLabel(platform_label, proxy_widget) + if self.multipath: + input_data_for_list["key"] = platform_key + input_widget = ListWidget( + input_data_for_list, values, self.keys, self, label_widget + ) + else: + input_data = {"key": platform_key} + input_widget = PathInputWidget( + input_data, values, self.keys, self, label_widget + ) + proxy_layout.addRow(label_widget, input_widget) + self.input_fields.append(input_widget) + input_widget.value_changed.connect(self._on_value_change) + + self.setFocusProxy(self.input_fields[0]) + self.content_layout.addWidget(proxy_widget) def update_global_values(self, values): + print(self.__class__.__name__, "* TODO implement `update_global_values`") value = NOT_SET if not self._as_widget: value = self.value_from_values(values) @@ -2480,6 +2519,7 @@ class PathWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value def set_value(self, value, *, global_value=False): + print(self.__class__.__name__, "* TODO implement `set_value`") self.text_input.setText(value) if global_value: self.start_value = self.item_value() @@ -2487,12 +2527,15 @@ class PathWidget(QtWidgets.QWidget, InputObject): self._on_value_change() def reset_value(self): + print(self.__class__.__name__, "* TODO implement `reset_value`") self.set_value(self.start_value) def clear_value(self): + print(self.__class__.__name__, "* TODO implement `clear_value`") self.set_value("") def _on_value_change(self, item=None): + print(self.__class__.__name__, "* TODO implement `_on_value_change`") if self.ignore_value_changes: return @@ -2505,29 +2548,25 @@ class PathWidget(QtWidgets.QWidget, InputObject): self.value_changed.emit(self) def update_style(self): - state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) + print(self.__class__.__name__, "* TODO implement `update_style`") def item_value(self): - return self.text_input.text() + if not self.multiplatform and not self.multipath: + return self.input_fields[0].item_value() + + if not self.multiplatform: + return self.input_fields[0].item_value() + + output = {} + for input_field in self.input_fields: + output.update(input_field.config_value()) + return output TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["text-singleline"] = TextSingleLineWidget TypeToKlass.types["path-input"] = PathInputWidget +TypeToKlass.types["path-widget"] = PathWidget TypeToKlass.types["text-multiline"] = TextMultiLineWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget From a8b4daa4646e66c326dd1c9926800cdc6a853c2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 11:09:13 +0200 Subject: [PATCH 667/947] PathInput moved to widgets --- .../config_setting/widgets/inputs.py | 32 ++----------------- .../config_setting/widgets/widgets.py | 31 +++++++++++++++++- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 63db5cb2e2..812d005153 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -4,7 +4,8 @@ from Qt import QtWidgets, QtCore, QtGui from .widgets import ( ExpandingWidget, ModifiedIntSpinBox, - ModifiedFloatSpinBox + ModifiedFloatSpinBox, + PathInput ) from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass @@ -2247,35 +2248,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): return values, self.is_group -class PathInput(QtWidgets.QLineEdit): - def clear_end_path(self): - value = self.text().strip() - if value.endswith("/"): - while value and value[-1] == "/": - value = value[:-1] - self.setText(value) - - def keyPressEvent(self, event): - # Always change backslash `\` for forwardslash `/` - if event.key() == QtCore.Qt.Key_Backslash: - event.accept() - new_event = QtGui.QKeyEvent( - event.type(), - QtCore.Qt.Key_Slash, - event.modifiers(), - "/", - event.isAutoRepeat(), - event.count() - ) - QtWidgets.QApplication.sendEvent(self, new_event) - return - super(PathInput, self).keyPressEvent(event) - - def focusOutEvent(self, event): - super(PathInput, self).focusOutEvent(event) - self.clear_end_path() - - class PathInputWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index db41fda1f6..8e0dda42fd 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets, QtCore +from Qt import QtWidgets, QtCore, QtGui class ModifiedIntSpinBox(QtWidgets.QSpinBox): @@ -35,6 +35,35 @@ class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): event.ignore() +class PathInput(QtWidgets.QLineEdit): + def clear_end_path(self): + value = self.text().strip() + if value.endswith("/"): + while value and value[-1] == "/": + value = value[:-1] + self.setText(value) + + def keyPressEvent(self, event): + # Always change backslash `\` for forwardslash `/` + if event.key() == QtCore.Qt.Key_Backslash: + event.accept() + new_event = QtGui.QKeyEvent( + event.type(), + QtCore.Qt.Key_Slash, + event.modifiers(), + "/", + event.isAutoRepeat(), + event.count() + ) + QtWidgets.QApplication.sendEvent(self, new_event) + return + super(PathInput, self).keyPressEvent(event) + + def focusOutEvent(self, event): + super(PathInput, self).focusOutEvent(event) + self.clear_end_path() + + class ClickableWidget(QtWidgets.QLabel): clicked = QtCore.Signal() From 78c88a9429cfb0c7b6de22da6b332c1a97aae383 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 11:16:49 +0200 Subject: [PATCH 668/947] fixed ListItem right click --- .../tools/config_setting/config_setting/widgets/inputs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 812d005153..dbc220d89a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1140,6 +1140,14 @@ class ListItem(QtWidgets.QWidget, ConfigObject): return self.value_input.item_value() return NOT_SET + @property + def child_modified(self): + return self.value_input.child_modified + + @property + def child_overriden(self): + return self.value_input.child_overriden + class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) From 22ec0a03b9116ce42e31c0d0ea71f4f05840d68e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 11:39:59 +0200 Subject: [PATCH 669/947] integer and lfoat widget merged into one number widget --- .../config_setting/widgets/widgets.py | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 8e0dda42fd..d5b0f088de 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -1,28 +1,12 @@ from Qt import QtWidgets, QtCore, QtGui -class ModifiedIntSpinBox(QtWidgets.QSpinBox): +class NumberSpinBox(QtWidgets.QDoubleSpinBox): def __init__(self, *args, **kwargs): min_value = kwargs.pop("minimum", -99999) max_value = kwargs.pop("maximum", 99999) - super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setMinimum(min_value) - self.setMaximum(max_value) - - def wheelEvent(self, event): - if self.hasFocus(): - super(ModifiedIntSpinBox, self).wheelEvent(event) - else: - event.ignore() - - -class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): - def __init__(self, *args, **kwargs): - min_value = kwargs.pop("minimum", -99999) - max_value = kwargs.pop("maximum", 99999) - decimals = kwargs.pop("decimal", 2) - super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) + decimals = kwargs.pop("decimal", 0) + super(NumberSpinBox, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setDecimals(decimals) self.setMinimum(min_value) @@ -30,7 +14,7 @@ class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): def wheelEvent(self, event): if self.hasFocus(): - super(ModifiedFloatSpinBox, self).wheelEvent(event) + super(NumberSpinBox, self).wheelEvent(event) else: event.ignore() From d0a5d2800650ac5a1165dfa5abf40faf1e72a081 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 11:40:52 +0200 Subject: [PATCH 670/947] integer and float input widgets also reduced to one input field with changeable "decimal" attribute --- .../projects_schema/1_plugins_gui_schema.json | 22 +-- .../studio_schema/0_studio_gui_schema.json | 2 +- .../studio_schema/1_tray_items.json | 10 +- .../config_setting/widgets/inputs.py | 154 ++---------------- 4 files changed, 33 insertions(+), 155 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 19d5127a95..e43518989a 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -39,7 +39,7 @@ "label": "Deadline apartment", "default": "" }, { - "type": "int", + "type": "number", "key": "deadline_priority", "label": "Deadline priority", "default": 50 @@ -59,7 +59,7 @@ "label": "Deadline Group", "default": "" }, { - "type": "int", + "type": "number", "key": "deadline_chunk_size", "label": "Deadline Chunk size", "default": 10 @@ -208,32 +208,32 @@ "label": "Burnin formating options", "children": [ { - "type": "int", + "type": "number", "key": "font_size", "label": "Font size", "default": 42 }, { - "type": "int", + "type": "number", "key": "opacity", "label": "Font opacity", "default": 1 }, { - "type": "int", + "type": "number", "key": "bg_opacity", "label": "Background opacity", "default": 1 }, { - "type": "int", + "type": "number", "key": "x_offset", "label": "X Offset", "default": 5 }, { - "type": "int", + "type": "number", "key": "y_offset", "label": "Y Offset", "default": 5 }, { - "type": "int", + "type": "number", "key": "bg_padding", "label": "Padding aroung text", "default": 5 @@ -525,7 +525,7 @@ "is_group": true, "children": [ { - "type": "int", + "type": "number", "key": "deadline_priority", "label": "deadline_priority", "default": 50 @@ -540,7 +540,7 @@ "label": "deadline_pool_secondary", "default": "" }, { - "type": "int", + "type": "number", "key": "deadline_chunk_size", "label": "deadline_chunk_size", "default": 1 @@ -639,7 +639,7 @@ "label": "Folder", "default": "takes" }, { - "type": "int", + "type": "number", "key": "steps", "label": "Steps", "default": 20 diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index db465fb392..bde340250e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -21,7 +21,7 @@ "key": "muster", "children": [{ "type": "dict-modifiable", - "object_type": "int", + "object_type": "number", "input_modifiers": { "minimum": 0, "maximum": 300 diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index 7cea724f29..610430b25e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -72,14 +72,14 @@ "expandable": true, "children": [ { - "type": "int", + "type": "number", "key": "default_port", "label": "Default Port", "minimum": 1, "maximum": 65535 }, { "type": "list", - "object_type": "int", + "object_type": "number", "key": "exclude_ports", "label": "Exclude ports", "input_modifiers": { @@ -95,11 +95,13 @@ "expandable": true, "children": [ { - "type": "float", + "type": "number", + "decimal": 2, "key": "full_time", "label": "Max idle time" }, { - "type": "float", + "type": "number", + "decimal": 2, "key": "message_time", "label": "When dialog will show" } diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index dbc220d89a..3e86b864b4 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -3,8 +3,7 @@ import logging from Qt import QtWidgets, QtCore, QtGui from .widgets import ( ExpandingWidget, - ModifiedIntSpinBox, - ModifiedFloatSpinBox, + NumberSpinBox, PathInput ) from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass @@ -446,13 +445,15 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): return self.checkbox.isChecked() -class IntegerWidget(QtWidgets.QWidget, InputObject): +class NumberWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) - input_modifiers = ("minimum", "maximum") + input_modifiers = ("minimum", "maximum", "decimal") def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + super(NumberWidget, self).__init__(parent) + self._parent = parent self._as_widget = values is AS_WIDGET @@ -462,8 +463,6 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): self._state = None - super(IntegerWidget, self).__init__(parent) - layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) @@ -473,15 +472,15 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): for modifier in self.input_modifiers if input_data.get(modifier) } - self.int_input = ModifiedIntSpinBox(self, **kwargs) + self.input_field = NumberSpinBox(self, **kwargs) - self.setFocusProxy(self.int_input) + self.setFocusProxy(self.input_field) if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) - layout.addWidget(self.int_input, 1) + layout.addWidget(self.input_field, 1) if not self._as_widget: self.label_widget = label_widget @@ -495,17 +494,17 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): self.override_value = NOT_SET - self.int_input.valueChanged.connect(self._on_value_change) + self.input_field.valueChanged.connect(self._on_value_change) def update_global_values(self, values): value = NOT_SET if not self._as_widget: value = self.value_from_values(values) if value is not NOT_SET: - self.int_input.setValue(value) + self.input_field.setValue(value) elif self.default_value is not NOT_SET: - self.int_input.setValue(self.default_value) + self.input_field.setValue(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -513,7 +512,7 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value def set_value(self, value, *, global_value=False): - self.int_input.setValue(value) + self.input_field.setValue(value) if global_value: self.start_value = self.item_value() self.global_value = self.item_value() @@ -546,7 +545,7 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): if self._as_widget: property_name = "input-state" - widget = self.int_input + widget = self.input_field else: property_name = "state" widget = self.label_widget @@ -555,129 +554,7 @@ class IntegerWidget(QtWidgets.QWidget, InputObject): widget.style().polish(widget) def item_value(self): - return self.int_input.value() - - -class FloatWidget(QtWidgets.QWidget, InputObject): - value_changed = QtCore.Signal(object) - input_modifiers = ("minimum", "maximum", "decimal") - - def __init__( - self, input_data, values, parent_keys, parent, label_widget=None - ): - self._parent = parent - self._as_widget = values is AS_WIDGET - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) - - self._state = None - - super(FloatWidget, self).__init__(parent) - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - kwargs = { - modifier: input_data.get(modifier) - for modifier in self.input_modifiers - if input_data.get(modifier) - } - self.float_input = ModifiedFloatSpinBox(self, **kwargs) - - self.setFocusProxy(self.float_input) - - decimals = input_data.get("decimals", 5) - maximum = input_data.get("maximum") - minimum = input_data.get("minimum") - - self.float_input.setDecimals(decimals) - if maximum is not None: - self.float_input.setMaximum(float(maximum)) - if minimum is not None: - self.float_input.setMinimum(float(minimum)) - - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - layout.addWidget(self.float_input, 1) - - if not self._as_widget: - self.label_widget = label_widget - - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) - - self.override_value = NOT_SET - - self.float_input.valueChanged.connect(self._on_value_change) - - def update_global_values(self, values): - value = NOT_SET - if not self._as_widget: - value = self.value_from_values(values) - if value is not NOT_SET: - self.float_input.setValue(value) - - elif self.default_value is not NOT_SET: - self.float_input.setValue(self.default_value) - - self.global_value = value - self.start_value = self.item_value() - - self._is_modified = self.global_value != self.start_value - - def set_value(self, value, *, global_value=False): - self.float_input.setValue(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.global_value) - - def clear_value(self): - self.set_value(0) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.global_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.float_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.float_input.value() + return self.input_field.value() class TextSingleLineWidget(QtWidgets.QWidget, InputObject): @@ -2549,8 +2426,7 @@ TypeToKlass.types["path-input"] = PathInputWidget TypeToKlass.types["path-widget"] = PathWidget TypeToKlass.types["text-multiline"] = TextMultiLineWidget TypeToKlass.types["raw-json"] = RawJsonWidget -TypeToKlass.types["int"] = IntegerWidget -TypeToKlass.types["float"] = FloatWidget +TypeToKlass.types["number"] = NumberWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict TypeToKlass.types["dict"] = DictWidget TypeToKlass.types["dict-form"] = DictFormWidget From d4a3a2ec17ed9fa87f8ddea823451d413432c513 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 11:56:06 +0200 Subject: [PATCH 671/947] changed order in boolean input --- .../config_setting/widgets/inputs.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3e86b864b4..0d2ff628ce 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -351,22 +351,20 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.checkbox = QtWidgets.QCheckBox(self) - - self.setFocusProxy(self.checkbox) - - self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - layout.addWidget(label_widget, 0) - - layout.addWidget(self.checkbox, 1) - if not self._as_widget: + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(label_widget, 0) self.label_widget = label_widget + self.checkbox = QtWidgets.QCheckBox(self) + self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(self.checkbox, 1) + self.setFocusProxy(self.checkbox) + + if not self._as_widget: self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) From e79b051b59330027e1080cf648f10dfa55766d21 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 12:43:24 +0200 Subject: [PATCH 672/947] cleanup anatomy input imports --- .../config_setting/widgets/anatomy_inputs.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index addd0d6666..feae91ba99 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,13 +1,6 @@ -import json -import logging -from Qt import QtWidgets, QtCore, QtGui -from .widgets import ( - ExpandingWidget, - ModifiedIntSpinBox, - ModifiedFloatSpinBox -) -from .inputs import ConfigObject, InputObject -from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass +from Qt import QtWidgets, QtCore +from .inputs import InputObject +from .lib import NOT_SET, AS_WIDGET, TypeToKlass class AnatomyWidget(QtWidgets.QWidget, InputObject): From 0ceb697c21be236711e179dcd15f90d8cea522a9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 12:45:52 +0200 Subject: [PATCH 673/947] changed way how global values a propagated to inputs --- .../config_setting/widgets/base.py | 8 +- .../config_setting/widgets/inputs.py | 361 ++++++++---------- 2 files changed, 164 insertions(+), 205 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 9c1fee5b6f..cd459aa674 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -92,10 +92,10 @@ class StudioWidget(QtWidgets.QWidget): widget.deleteLater() self.input_fields.clear() - values = {"studio": config.studio_configurations()} self.schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") self.keys = self.schema.get("keys", []) - self.add_children_gui(self.schema, values) + self.add_children_gui(self.schema, lib.NOT_SET) + self._update_global_values() self.hierarchical_style_update() def _save(self): @@ -404,10 +404,10 @@ class ProjectWidget(QtWidgets.QWidget): input_field.hierarchical_style_update() def reset(self): - values = {"project": config.global_project_configurations()} self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = self.schema.get("keys", []) - self.add_children_gui(self.schema, values) + self.add_children_gui(self.schema, lib.NOT_SET) + self._update_global_values() self.hierarchical_style_update() def add_children_gui(self, child_configuration, values): diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 0d2ff628ce..5f5747fd71 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -120,26 +120,6 @@ class ConfigObject: """Output for saving changes or overrides.""" return {self.key: self.item_value()} - def value_from_values(self, values, keys=None): - """Global getter of value based on loaded values.""" - if not values or values is AS_WIDGET: - return NOT_SET - - if keys is None: - keys = self.keys - - value = values - for key in keys: - if not isinstance(value, dict): - raise TypeError( - "Expected dictionary got {}.".format(str(type(value))) - ) - - if key not in value: - return NOT_SET - value = value[key] - return value - def style_state(self, is_invalid, is_overriden, is_modified): items = [] if is_invalid: @@ -336,22 +316,26 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + super(BooleanWidget, self).__init__(parent) + self._parent = parent self._as_widget = values is AS_WIDGET + self._state = None self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self._state = None - - super(BooleanWidget, self).__init__(parent) + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) if not self._as_widget: + self.key = input_data["key"] if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) @@ -364,21 +348,14 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): layout.addWidget(self.checkbox, 1) self.setFocusProxy(self.checkbox) - if not self._as_widget: - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) - self.override_value = NOT_SET - self.checkbox.stateChanged.connect(self._on_value_change) - def update_global_values(self, values): + def update_global_values(self, parent_values): value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.checkbox.setChecked(value) @@ -454,12 +431,15 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._as_widget = values is AS_WIDGET + self._state = None self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self._state = None + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -474,30 +454,24 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.input_field) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - layout.addWidget(self.input_field, 1) - if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) - - self.override_value = NOT_SET + layout.addWidget(self.input_field, 1) self.input_field.valueChanged.connect(self._on_value_change) - def update_global_values(self, values): + def update_global_values(self, parent_values): value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.input_field.setValue(value) @@ -561,16 +535,19 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + super(TextSingleLineWidget, self).__init__(parent) + self._parent = parent self._as_widget = values is AS_WIDGET + self._state = None self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self._state = None - - super(TextSingleLineWidget, self).__init__(parent) + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -580,30 +557,24 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.text_input) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - layout.addWidget(self.text_input, 1) - if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) - - self.override_value = NOT_SET + layout.addWidget(self.text_input, 1) self.text_input.textChanged.connect(self._on_value_change) - def update_global_values(self, values): + def update_global_values(self, parent_values): value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.text_input.setText(value) @@ -667,16 +638,19 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + super(TextMultiLineWidget, self).__init__(parent) + self._parent = parent self._as_widget = values is AS_WIDGET + self._state = None self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self._state = None - - super(TextMultiLineWidget, self).__init__(parent) + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -686,30 +660,23 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.text_input) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - layout.addWidget(self.text_input, 1) - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) - - self.override_value = NOT_SET + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + layout.addWidget(self.text_input, 1) self.text_input.textChanged.connect(self._on_value_change) - def update_global_values(self, values): + def update_global_values(self, parent_values): value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.text_input.setPlainText(value) @@ -825,8 +792,11 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + super(RawJsonWidget, self).__init__(parent) + self._parent = parent self._as_widget = values is AS_WIDGET + self._state = None any_parent_is_group = parent.is_group if not any_parent_is_group: @@ -838,9 +808,9 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self._state = None - - super(RawJsonWidget, self).__init__(parent) + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -854,30 +824,23 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.text_input) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - layout.addWidget(self.text_input, 1) - - self.override_value = NOT_SET - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + layout.addWidget(self.text_input, 1) self.text_input.textChanged.connect(self._on_value_change) - def update_global_values(self, values): + def update_global_values(self, parent_values): value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.text_input.set_value(value) @@ -1030,12 +993,12 @@ class ListWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): - self._parent = parent - super(ListWidget, self).__init__(parent) self.setObjectName("ListWidget") + self._parent = parent self._state = None + self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) @@ -1043,18 +1006,14 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET + + self.key = input_data["key"] + self.input_fields = [] - inputs_widget = QtWidgets.QWidget(self) - inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) - - inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) - inputs_layout.setContentsMargins(0, 5, 0, 5) - inputs_layout.setSpacing(3) - - self.inputs_widget = inputs_widget - self.inputs_layout = inputs_layout - layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) @@ -1063,19 +1022,18 @@ class ListWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) - self.label_widget = label_widget + inputs_widget = QtWidgets.QWidget(self) + inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) layout.addWidget(inputs_widget) - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys + inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) + inputs_layout.setContentsMargins(0, 5, 0, 5) + inputs_layout.setSpacing(3) - self.update_global_values(values) - - self.override_value = NOT_SET + self.inputs_widget = inputs_widget + self.inputs_layout = inputs_layout def count(self): return len(self.input_fields) @@ -1086,10 +1044,12 @@ class ListWidget(QtWidgets.QWidget, InputObject): def clear_value(self): self.set_value([]) - def update_global_values(self, values): + def update_global_values(self, parent_values): old_inputs = tuple(self.input_fields) - value = self.value_from_values(values) + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) self.global_value = value @@ -1227,10 +1187,10 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__(self, object_type, input_modifiers, config_parent, parent): - self._parent = config_parent - super(ModifiableDictItem, self).__init__(parent) + self._parent = config_parent + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) @@ -1344,13 +1304,18 @@ class ModifiableDict(ExpandingWidget, InputObject): self, input_data, values, parent_keys, parent, label_widget=None ): - self._parent = parent - super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") + self._parent = parent self._state = None + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET + + self.key = input_data["key"] + self.input_fields = [] any_parent_is_group = parent.is_group @@ -1378,22 +1343,15 @@ class ModifiableDict(ExpandingWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.override_value = NOT_SET - - self.update_global_values(values) - def count(self): return len(self.input_fields) - def update_global_values(self, values): + def update_global_values(self, parent_values): old_inputs = tuple(self.input_fields) - value = self.value_from_values(values) + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) self.global_value = value @@ -1566,9 +1524,6 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(5, 5, 0, 5) @@ -1596,7 +1551,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.checkbox_key = input_data.get("checkbox_key") for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) + self.add_children_gui(child_data) expandable = input_data.get("expandable", True) if len(self.input_fields) == 1 and self.checkbox_widget: @@ -1634,9 +1589,13 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() - def update_global_values(self, values): + def update_global_values(self, parent_values): + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + for item in self.input_fields: - item.update_global_values(values) + item.update_global_values(value) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -1749,16 +1708,16 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): output.update(input_field.config_value()) return output - def add_children_gui(self, child_configuration, values): + def add_children_gui(self, child_configuration): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) if self.checkbox_key and not self.checkbox_widget: key = child_configuration.get("key") if key == self.checkbox_key: - return self._add_checkbox_child(child_configuration, values) + return self._add_checkbox_child(child_configuration) item = klass( - child_configuration, values, self.keys, self + child_configuration, NOT_SET, self.keys, self ) item.value_changed.connect(self._on_value_change) self.content_layout.addWidget(item) @@ -1766,9 +1725,9 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.input_fields.append(item) return item - def _add_checkbox_child(self, child_configuration, values): + def _add_checkbox_child(self, child_configuration): item = BooleanWidget( - child_configuration, values, self.keys, self, self.label_widget + child_configuration, NOT_SET, self.keys, self, self.label_widget ) item.value_changed.connect(self._on_value_change) @@ -1823,11 +1782,9 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.key = input_data["key"] - self.keys = list(parent_keys) - self.keys.append(self.key) for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) + self.add_children_gui(child_data) def update_style(self, *args, **kwargs): return @@ -1867,12 +1824,12 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): output.update(input_field.config_value()) return output - def add_children_gui(self, child_configuration, values): + def add_children_gui(self, child_configuration): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) item = klass( - child_configuration, values, self.keys, self + child_configuration, NOT_SET, self.keys, self ) self.layout().addWidget(item) @@ -1922,9 +1879,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() - def update_global_values(self, values): + def update_global_values(self, parent_values): + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + for item in self.input_fields: - item.update_global_values(values) + item.update_global_values(value) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -2001,7 +1962,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self.keys = list(parent_keys) for child_data in input_data.get("children", []): - self.add_children_gui(child_data, values) + self.add_children_gui(child_data) def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton: @@ -2042,9 +2003,9 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() - def update_global_values(self, values): + def update_global_values(self, value): for item in self.input_fields: - item.update_global_values(values) + item.update_global_values(value) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -2080,7 +2041,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): output.extend(input_field.get_invalid()) return output - def add_children_gui(self, child_configuration, values): + def add_children_gui(self, child_configuration): item_type = child_configuration["type"] # Pop label to not be set in child label = child_configuration["label"] @@ -2090,7 +2051,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): label_widget = FormLabel(label, self) item = klass( - child_configuration, values, self.keys, self, label_widget + child_configuration, NOT_SET, self.keys, self, label_widget ) label_widget.item = item @@ -2137,16 +2098,19 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): + super(PathInputWidget, self).__init__(parent) + self._parent = parent self._as_widget = values is AS_WIDGET + self._state = None self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self._state = None - - super(PathInputWidget, self).__init__(parent) + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -2156,30 +2120,23 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.path_input) - if not self._as_widget and not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - layout.addWidget(self.path_input, 1) - if not self._as_widget: - self.label_widget = label_widget - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - - self.update_global_values(values) - - self.override_value = NOT_SET + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + layout.addWidget(self.path_input, 1) self.path_input.textChanged.connect(self._on_value_change) - def update_global_values(self, values): + def update_global_values(self, parent_values): value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.path_input.setText(value) @@ -2305,15 +2262,13 @@ class PathWidget(QtWidgets.QWidget, InputObject): layout.addWidget(self.content_widget) - self.create_gui(values) + self.create_gui() - self.update_global_values(values) - - def create_gui(self, values): + def create_gui(self): if not self.multiplatform and not self.multipath: input_data = {"key": self.key} path_input = PathInputWidget( - input_data, values, self.keys, self, self.label_widget + input_data, NOT_SET, self.keys, self, self.label_widget ) self.setFocusProxy(path_input) self.content_layout.addWidget(path_input) @@ -2327,7 +2282,7 @@ class PathWidget(QtWidgets.QWidget, InputObject): if not self.multiplatform: input_data_for_list["key"] = self.key input_widget = ListWidget( - input_data_for_list, values, self.keys, self, self.label_widget + input_data_for_list, NOT_SET, self.keys, self, self.label_widget ) self.setFocusProxy(input_widget) self.content_layout.addWidget(input_widget) @@ -2343,12 +2298,12 @@ class PathWidget(QtWidgets.QWidget, InputObject): if self.multipath: input_data_for_list["key"] = platform_key input_widget = ListWidget( - input_data_for_list, values, self.keys, self, label_widget + input_data_for_list, NOT_SET, self.keys, self, label_widget ) else: input_data = {"key": platform_key} input_widget = PathInputWidget( - input_data, values, self.keys, self, label_widget + input_data, NOT_SET, self.keys, self, label_widget ) proxy_layout.addRow(label_widget, input_widget) self.input_fields.append(input_widget) @@ -2357,11 +2312,15 @@ class PathWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.input_fields[0]) self.content_layout.addWidget(proxy_widget) - def update_global_values(self, values): - print(self.__class__.__name__, "* TODO implement `update_global_values`") + def update_global_values(self, parent_values): + print( + self.__class__.__name__, "* TODO implement `update_global_values`" + ) value = NOT_SET if not self._as_widget: - value = self.value_from_values(values) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + if value is not NOT_SET: self.text_input.setText(value) From 370705a10a286770c1eb67994da67d2495762496 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 13:07:13 +0200 Subject: [PATCH 674/947] removed AS_WIDGET constant and reduced input args --- .../config_setting/widgets/anatomy_inputs.py | 6 +- .../config_setting/widgets/base.py | 16 +- .../config_setting/widgets/inputs.py | 193 ++++++++---------- .../config_setting/widgets/lib.py | 1 - 4 files changed, 95 insertions(+), 121 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index feae91ba99..7c9ac0d8f4 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,16 +1,16 @@ from Qt import QtWidgets, QtCore from .inputs import InputObject -from .lib import NOT_SET, AS_WIDGET, TypeToKlass +from .lib import NOT_SET, TypeToKlass class AnatomyWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._is_group = True self._state = None diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index cd459aa674..f057f5a0f6 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -94,7 +94,7 @@ class StudioWidget(QtWidgets.QWidget): self.schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") self.keys = self.schema.get("keys", []) - self.add_children_gui(self.schema, lib.NOT_SET) + self.add_children_gui(self.schema) self._update_global_values() self.hierarchical_style_update() @@ -175,12 +175,10 @@ class StudioWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.hierarchical_style_update() - def add_children_gui(self, child_configuration, values): + def add_children_gui(self, child_configuration): item_type = child_configuration["type"] klass = lib.TypeToKlass.types.get(item_type) - item = klass( - child_configuration, values, self.keys, self - ) + item = klass(child_configuration, self) self.input_fields.append(item) self.content_layout.addWidget(item) @@ -406,16 +404,14 @@ class ProjectWidget(QtWidgets.QWidget): def reset(self): self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = self.schema.get("keys", []) - self.add_children_gui(self.schema, lib.NOT_SET) + self.add_children_gui(self.schema) self._update_global_values() self.hierarchical_style_update() - def add_children_gui(self, child_configuration, values): + def add_children_gui(self, child_configuration): item_type = child_configuration["type"] klass = lib.TypeToKlass.types.get(item_type) - item = klass( - child_configuration, values, self.keys, self - ) + item = klass(child_configuration, self) self.input_fields.append(item) self.content_layout.addWidget(item) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 5f5747fd71..336a8ef93e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -6,7 +6,7 @@ from .widgets import ( NumberSpinBox, PathInput ) -from .lib import NOT_SET, AS_WIDGET, METADATA_KEY, TypeToKlass +from .lib import NOT_SET, METADATA_KEY, TypeToKlass class ConfigObject: @@ -314,12 +314,12 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(BooleanWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._state = None self._is_group = input_data.get("is_group", False) @@ -334,7 +334,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: + if not as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] @@ -425,12 +425,12 @@ class NumberWidget(QtWidgets.QWidget, InputObject): input_modifiers = ("minimum", "maximum", "decimal") def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(NumberWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._state = None self._is_group = input_data.get("is_group", False) @@ -533,12 +533,12 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(TextSingleLineWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._state = None self._is_group = input_data.get("is_group", False) @@ -636,12 +636,12 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(TextMultiLineWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._state = None self._is_group = input_data.get("is_group", False) @@ -790,12 +790,12 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(RawJsonWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._state = None any_parent_is_group = parent.is_group @@ -944,10 +944,9 @@ class ListItem(QtWidgets.QWidget, ConfigObject): ItemKlass = TypeToKlass.types[object_type] self.value_input = ItemKlass( input_modifiers, - AS_WIDGET, - [], self, - None + as_widget=True, + label_widget=None ) layout.addWidget(self.value_input, 1) @@ -991,7 +990,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(ListWidget, self).__init__(parent) self.setObjectName("ListWidget") @@ -1202,10 +1201,9 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.value_input = ItemKlass( input_modifiers, - AS_WIDGET, - [], self, - None + as_widget=True, + label_widget=None ) self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") @@ -1301,8 +1299,7 @@ class ModifiableDict(ExpandingWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, - label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") @@ -1498,9 +1495,9 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): - if values is AS_WIDGET: + if as_widget: raise TypeError("Can't use \"{}\" as widget item.".format( self.__class__.__name__ )) @@ -1564,6 +1561,32 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): else: body_widget.hide_toolbox(hide_content=False) + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + if self.checkbox_key and not self.checkbox_widget: + key = child_configuration.get("key") + if key == self.checkbox_key: + return self._add_checkbox_child(child_configuration) + + item = klass(child_configuration, self) + item.value_changed.connect(self._on_value_change) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + def _add_checkbox_child(self, child_configuration): + item = BooleanWidget( + child_configuration, self, label_widget=self.label_widget + ) + item.value_changed.connect(self._on_value_change) + + self.body_widget.top_part.layout().addWidget(item) + self.checkbox_widget = item + self.input_fields.append(item) + return item + def remove_overrides(self): self._is_overriden = False self._is_modified = False @@ -1708,34 +1731,6 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): output.update(input_field.config_value()) return output - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - if self.checkbox_key and not self.checkbox_widget: - key = child_configuration.get("key") - if key == self.checkbox_key: - return self._add_checkbox_child(child_configuration) - - item = klass( - child_configuration, NOT_SET, self.keys, self - ) - item.value_changed.connect(self._on_value_change) - self.content_layout.addWidget(item) - - self.input_fields.append(item) - return item - - def _add_checkbox_child(self, child_configuration): - item = BooleanWidget( - child_configuration, NOT_SET, self.keys, self, self.label_widget - ) - item.value_changed.connect(self._on_value_change) - - self.body_widget.top_part.layout().addWidget(item) - self.checkbox_widget = item - self.input_fields.append(item) - return item - def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False @@ -1759,7 +1754,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): allow_actions = False def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): self._parent = parent @@ -1786,6 +1781,18 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): for child_data in input_data.get("children", []): self.add_children_gui(child_data) + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass(child_configuration, self) + self.layout().addWidget(item) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + def update_style(self, *args, **kwargs): return @@ -1824,20 +1831,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): output.update(input_field.config_value()) return output - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - klass = TypeToKlass.types.get(item_type) - - item = klass( - child_configuration, NOT_SET, self.keys, self - ) - self.layout().addWidget(item) - - item.value_changed.connect(self._on_value_change) - - self.input_fields.append(item) - return item - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1942,7 +1935,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): allow_actions = False def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): self._parent = parent @@ -1959,11 +1952,26 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.content_layout = QtWidgets.QFormLayout(self) - self.keys = list(parent_keys) - for child_data in input_data.get("children", []): self.add_children_gui(child_data) + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = FormLabel(label, self) + + item = klass(child_configuration, self, label_widget=label_widget) + label_widget.item = item + + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields.append(item) + return item + def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton: position = self.mapFromGlobal(QtGui.QCursor().pos()) @@ -2041,25 +2049,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): output.extend(input_field.get_invalid()) return output - def add_children_gui(self, child_configuration): - item_type = child_configuration["type"] - # Pop label to not be set in child - label = child_configuration["label"] - - klass = TypeToKlass.types.get(item_type) - - label_widget = FormLabel(label, self) - - item = klass( - child_configuration, NOT_SET, self.keys, self, label_widget - ) - label_widget.item = item - - item.value_changed.connect(self._on_value_change) - self.content_layout.addRow(label_widget, item) - self.input_fields.append(item) - return item - def hierarchical_style_update(self): for input_field in self.input_fields: input_field.hierarchical_style_update() @@ -2096,12 +2085,12 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(PathInputWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET + self._as_widget = as_widget self._state = None self._is_group = input_data.get("is_group", False) @@ -2214,12 +2203,11 @@ class PathWidget(QtWidgets.QWidget, InputObject): } def __init__( - self, input_data, values, parent_keys, parent, label_widget=None + self, input_data, parent, as_widget=False, label_widget=None ): super(PathWidget, self).__init__(parent) self._parent = parent - self._as_widget = values is AS_WIDGET self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) @@ -2234,12 +2222,6 @@ class PathWidget(QtWidgets.QWidget, InputObject): self.input_fields = [] - if not self._as_widget: - self.key = input_data["key"] - keys = list(parent_keys) - keys.append(self.key) - self.keys = keys - if not self.multiplatform and not self.multipath: layout = QtWidgets.QHBoxLayout(self) else: @@ -2247,13 +2229,12 @@ class PathWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget and not label_widget: + self.key = input_data["key"] + if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) - self.label_widget = label_widget - layout.addWidget(label_widget) self.content_widget = QtWidgets.QWidget(self) self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) @@ -2267,9 +2248,7 @@ class PathWidget(QtWidgets.QWidget, InputObject): def create_gui(self): if not self.multiplatform and not self.multipath: input_data = {"key": self.key} - path_input = PathInputWidget( - input_data, NOT_SET, self.keys, self, self.label_widget - ) + path_input = PathInputWidget(input_data, self, self.label_widget) self.setFocusProxy(path_input) self.content_layout.addWidget(path_input) self.input_fields.append(path_input) @@ -2282,7 +2261,7 @@ class PathWidget(QtWidgets.QWidget, InputObject): if not self.multiplatform: input_data_for_list["key"] = self.key input_widget = ListWidget( - input_data_for_list, NOT_SET, self.keys, self, self.label_widget + input_data_for_list, self, self.label_widget ) self.setFocusProxy(input_widget) self.content_layout.addWidget(input_widget) @@ -2298,12 +2277,12 @@ class PathWidget(QtWidgets.QWidget, InputObject): if self.multipath: input_data_for_list["key"] = platform_key input_widget = ListWidget( - input_data_for_list, NOT_SET, self.keys, self, label_widget + input_data_for_list, self, label_widget ) else: input_data = {"key": platform_key} input_widget = PathInputWidget( - input_data, NOT_SET, self.keys, self, label_widget + input_data, self, label_widget ) proxy_layout.addRow(label_widget, input_widget) self.input_fields.append(input_widget) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index c416f7a5b0..4669004b53 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -12,7 +12,6 @@ class TypeToKlass: NOT_SET = type("NOT_SET", (), {}) -AS_WIDGET = type("AS_WIDGET", (), {}) METADATA_KEY = type("METADATA_KEY", (), {}) OVERRIDE_VERSION = 1 From 9c34ee32abd971a80ff4c4fb44f26d23d96bda65 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 16:37:03 +0200 Subject: [PATCH 675/947] few updates and fixes for path input --- .../config_setting/widgets/inputs.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 336a8ef93e..53054b27a6 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2105,10 +2105,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.path_input = PathInput(self) - - self.setFocusProxy(self.path_input) - if not self._as_widget: self.key = input_data["key"] if not label_widget: @@ -2116,6 +2112,9 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) self.label_widget = label_widget + + self.path_input = PathInput(self) + self.setFocusProxy(self.path_input) layout.addWidget(self.path_input, 1) self.path_input.textChanged.connect(self._on_value_change) @@ -2217,6 +2216,8 @@ class PathWidget(QtWidgets.QWidget, InputObject): self.multipath = input_data.get("multipath", False) self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET self._state = None @@ -2248,7 +2249,9 @@ class PathWidget(QtWidgets.QWidget, InputObject): def create_gui(self): if not self.multiplatform and not self.multipath: input_data = {"key": self.key} - path_input = PathInputWidget(input_data, self, self.label_widget) + path_input = PathInputWidget( + input_data, self, label_widget=self.label_widget + ) self.setFocusProxy(path_input) self.content_layout.addWidget(path_input) self.input_fields.append(path_input) @@ -2261,7 +2264,7 @@ class PathWidget(QtWidgets.QWidget, InputObject): if not self.multiplatform: input_data_for_list["key"] = self.key input_widget = ListWidget( - input_data_for_list, self, self.label_widget + input_data_for_list, self, label_widget=self.label_widget ) self.setFocusProxy(input_widget) self.content_layout.addWidget(input_widget) @@ -2277,12 +2280,12 @@ class PathWidget(QtWidgets.QWidget, InputObject): if self.multipath: input_data_for_list["key"] = platform_key input_widget = ListWidget( - input_data_for_list, self, label_widget + input_data_for_list, self, label_widget=label_widget ) else: input_data = {"key": platform_key} input_widget = PathInputWidget( - input_data, self, label_widget + input_data, self, label_widget=label_widget ) proxy_layout.addRow(label_widget, input_widget) self.input_fields.append(input_widget) From e3a62eae3fd2cd17fa186eb1982f56017c7a24be Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 16:37:15 +0200 Subject: [PATCH 676/947] path widget can update global values --- .../config_setting/widgets/inputs.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 53054b27a6..23068ce236 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2295,19 +2295,16 @@ class PathWidget(QtWidgets.QWidget, InputObject): self.content_layout.addWidget(proxy_widget) def update_global_values(self, parent_values): - print( - self.__class__.__name__, "* TODO implement `update_global_values`" - ) value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if value is not NOT_SET: - self.text_input.setText(value) + if not self.multiplatform: + self.input_fields[0].update_global_values(parent_values) - elif self.default_value is not NOT_SET: - self.text_input.setText(self.default_value) + elif self.multiplatform: + for input_field in self.input_fields: + input_field.update_global_values(value) self.global_value = value self.start_value = self.item_value() From 4c0f0a6bddcd688f255d6475db8e196be50d559d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 16:45:18 +0200 Subject: [PATCH 677/947] reorganization --- .../config_setting/widgets/inputs.py | 572 +++++++++--------- 1 file changed, 289 insertions(+), 283 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 23068ce236..ac08a9ae4f 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -734,6 +734,111 @@ class TextMultiLineWidget(QtWidgets.QWidget, InputObject): return self.text_input.toPlainText() +class PathInputWidget(QtWidgets.QWidget, InputObject): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, as_widget=False, label_widget=None + ): + super(PathInputWidget, self).__init__(parent) + + self._parent = parent + self._as_widget = as_widget + self._state = None + + self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) + self.default_value = input_data.get("default", NOT_SET) + + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + self.path_input = PathInput(self) + self.setFocusProxy(self.path_input) + layout.addWidget(self.path_input, 1) + + self.path_input.textChanged.connect(self._on_value_change) + + def update_global_values(self, parent_values): + value = NOT_SET + if not self._as_widget: + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if value is not NOT_SET: + self.path_input.setText(value) + + elif self.default_value is not NOT_SET: + self.path_input.setText(self.default_value) + + self.global_value = value + self.start_value = self.item_value() + + self._is_modified = self.global_value != self.start_value + + def set_value(self, value, *, global_value=False): + self.path_input.setText(value) + if global_value: + self.start_value = self.item_value() + self.global_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.start_value) + + def clear_value(self): + self.set_value("") + + def focusOutEvent(self, event): + self.path_input.clear_end_path() + super(PathInput, self).focusOutEvent(event) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.global_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.path_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.path_input.text() + + class RawJsonInput(QtWidgets.QPlainTextEdit): tab_length = 4 @@ -1923,13 +2028,189 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): return {self.key: values}, self.is_group +class PathWidget(QtWidgets.QWidget, InputObject): + value_changed = QtCore.Signal(object) + + platforms = ("windows", "darwin", "linux") + platform_labels_mapping = { + "windows": "Windows", + "darwin": "MacOS", + "linux": "Linux" + } + platform_separators = { + "windows": ";", + "darwin": ":", + "linux": ":" + } + + def __init__( + self, input_data, parent, as_widget=False, label_widget=None + ): + super(PathWidget, self).__init__(parent) + + self._parent = parent + + self._is_group = input_data.get("is_group", False) + self._is_nullable = input_data.get("is_nullable", False) + + self.default_value = input_data.get("default", NOT_SET) + self.multiplatform = input_data.get("multiplatform", False) + self.multipath = input_data.get("multipath", False) + + self.override_value = NOT_SET + self.global_value = NOT_SET + self.start_value = NOT_SET + + self._state = None + + self.input_fields = [] + + if not self.multiplatform and not self.multipath: + layout = QtWidgets.QHBoxLayout(self) + else: + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + self.content_widget = QtWidgets.QWidget(self) + self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) + self.content_layout.setSpacing(0) + self.content_layout.setContentsMargins(0, 0, 0, 0) + + layout.addWidget(self.content_widget) + + self.create_gui() + + def create_gui(self): + if not self.multiplatform and not self.multipath: + input_data = {"key": self.key} + path_input = PathInputWidget( + input_data, self, label_widget=self.label_widget + ) + self.setFocusProxy(path_input) + self.content_layout.addWidget(path_input) + self.input_fields.append(path_input) + path_input.value_changed.connect(self._on_value_change) + return + + input_data_for_list = { + "object_type": "path-input" + } + if not self.multiplatform: + input_data_for_list["key"] = self.key + input_widget = ListWidget( + input_data_for_list, self, label_widget=self.label_widget + ) + self.setFocusProxy(input_widget) + self.content_layout.addWidget(input_widget) + self.input_fields.append(input_widget) + input_widget.value_changed.connect(self._on_value_change) + return + + proxy_widget = QtWidgets.QWidget(self.content_widget) + proxy_layout = QtWidgets.QFormLayout(proxy_widget) + for platform_key in self.platforms: + platform_label = self.platform_labels_mapping[platform_key] + label_widget = QtWidgets.QLabel(platform_label, proxy_widget) + if self.multipath: + input_data_for_list["key"] = platform_key + input_widget = ListWidget( + input_data_for_list, self, label_widget=label_widget + ) + else: + input_data = {"key": platform_key} + input_widget = PathInputWidget( + input_data, self, label_widget=label_widget + ) + proxy_layout.addRow(label_widget, input_widget) + self.input_fields.append(input_widget) + input_widget.value_changed.connect(self._on_value_change) + + self.setFocusProxy(self.input_fields[0]) + self.content_layout.addWidget(proxy_widget) + + def update_global_values(self, parent_values): + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if not self.multiplatform: + self.input_fields[0].update_global_values(parent_values) + + elif self.multiplatform: + for input_field in self.input_fields: + input_field.update_global_values(value) + + self.global_value = value + self.start_value = self.item_value() + + self._is_modified = self.global_value != self.start_value + + def set_value(self, value, *, global_value=False): + if not self.multiplatform: + self.input_fields[0].set_value(value, global_value=global_value) + + else: + for input_field in self.input_fields: + _value = value[input_field.key] + input_field.set_value(_value) + + if global_value: + self.global_value = value + self.start_value = self.item_value() + self._on_value_change() + + def reset_value(self): + for input_field in self.input_fields: + input_field.reset_value() + + def clear_value(self): + for input_field in self.input_fields: + input_field.clear_value() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.global_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + for input_field in self.input_fields: + input_field.update_style() + + def item_value(self): + if not self.multiplatform and not self.multipath: + return self.input_fields[0].item_value() + + if not self.multiplatform: + return self.input_fields[0].item_value() + + output = {} + for input_field in self.input_fields: + output.update(input_field.config_value()) + return output + + +# Proxy for form layout class FormLabel(QtWidgets.QLabel): def __init__(self, *args, **kwargs): super(FormLabel, self).__init__(*args, **kwargs) self.item = None -# Proxy for form layout class DictFormWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) allow_actions = False @@ -2081,290 +2362,15 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): return values, self.is_group -class PathInputWidget(QtWidgets.QWidget, InputObject): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent, as_widget=False, label_widget=None - ): - super(PathInputWidget, self).__init__(parent) - - self._parent = parent - self._as_widget = as_widget - self._state = None - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) - - self.override_value = NOT_SET - self.global_value = NOT_SET - self.start_value = NOT_SET - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - if not self._as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - self.label_widget = label_widget - - self.path_input = PathInput(self) - self.setFocusProxy(self.path_input) - layout.addWidget(self.path_input, 1) - - self.path_input.textChanged.connect(self._on_value_change) - - def update_global_values(self, parent_values): - value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.path_input.setText(value) - - elif self.default_value is not NOT_SET: - self.path_input.setText(self.default_value) - - self.global_value = value - self.start_value = self.item_value() - - self._is_modified = self.global_value != self.start_value - - def set_value(self, value, *, global_value=False): - self.path_input.setText(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.start_value) - - def clear_value(self): - self.set_value("") - - def focusOutEvent(self, event): - self.path_input.clear_end_path() - super(PathInput, self).focusOutEvent(event) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.global_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.path_input - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.path_input.text() - - -class PathWidget(QtWidgets.QWidget, InputObject): - value_changed = QtCore.Signal(object) - - platforms = ("windows", "darwin", "linux") - platform_labels_mapping = { - "windows": "Windows", - "darwin": "MacOS", - "linux": "Linux" - } - platform_separators = { - "windows": ";", - "darwin": ":", - "linux": ":" - } - - def __init__( - self, input_data, parent, as_widget=False, label_widget=None - ): - super(PathWidget, self).__init__(parent) - - self._parent = parent - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - - self.default_value = input_data.get("default", NOT_SET) - self.multiplatform = input_data.get("multiplatform", False) - self.multipath = input_data.get("multipath", False) - - self.override_value = NOT_SET - self.global_value = NOT_SET - self.start_value = NOT_SET - - self._state = None - - self.input_fields = [] - - if not self.multiplatform and not self.multipath: - layout = QtWidgets.QHBoxLayout(self) - else: - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - self.label_widget = label_widget - - self.content_widget = QtWidgets.QWidget(self) - self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) - self.content_layout.setSpacing(0) - self.content_layout.setContentsMargins(0, 0, 0, 0) - - layout.addWidget(self.content_widget) - - self.create_gui() - - def create_gui(self): - if not self.multiplatform and not self.multipath: - input_data = {"key": self.key} - path_input = PathInputWidget( - input_data, self, label_widget=self.label_widget - ) - self.setFocusProxy(path_input) - self.content_layout.addWidget(path_input) - self.input_fields.append(path_input) - path_input.value_changed.connect(self._on_value_change) - return - - input_data_for_list = { - "object_type": "path-input" - } - if not self.multiplatform: - input_data_for_list["key"] = self.key - input_widget = ListWidget( - input_data_for_list, self, label_widget=self.label_widget - ) - self.setFocusProxy(input_widget) - self.content_layout.addWidget(input_widget) - self.input_fields.append(input_widget) - input_widget.value_changed.connect(self._on_value_change) - return - - proxy_widget = QtWidgets.QWidget(self.content_widget) - proxy_layout = QtWidgets.QFormLayout(proxy_widget) - for platform_key in self.platforms: - platform_label = self.platform_labels_mapping[platform_key] - label_widget = QtWidgets.QLabel(platform_label, proxy_widget) - if self.multipath: - input_data_for_list["key"] = platform_key - input_widget = ListWidget( - input_data_for_list, self, label_widget=label_widget - ) - else: - input_data = {"key": platform_key} - input_widget = PathInputWidget( - input_data, self, label_widget=label_widget - ) - proxy_layout.addRow(label_widget, input_widget) - self.input_fields.append(input_widget) - input_widget.value_changed.connect(self._on_value_change) - - self.setFocusProxy(self.input_fields[0]) - self.content_layout.addWidget(proxy_widget) - - def update_global_values(self, parent_values): - value = NOT_SET - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if not self.multiplatform: - self.input_fields[0].update_global_values(parent_values) - - elif self.multiplatform: - for input_field in self.input_fields: - input_field.update_global_values(value) - - self.global_value = value - self.start_value = self.item_value() - - self._is_modified = self.global_value != self.start_value - - def set_value(self, value, *, global_value=False): - print(self.__class__.__name__, "* TODO implement `set_value`") - self.text_input.setText(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() - - def reset_value(self): - print(self.__class__.__name__, "* TODO implement `reset_value`") - self.set_value(self.start_value) - - def clear_value(self): - print(self.__class__.__name__, "* TODO implement `clear_value`") - self.set_value("") - - def _on_value_change(self, item=None): - print(self.__class__.__name__, "* TODO implement `_on_value_change`") - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.global_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - print(self.__class__.__name__, "* TODO implement `update_style`") - - def item_value(self): - if not self.multiplatform and not self.multipath: - return self.input_fields[0].item_value() - - if not self.multiplatform: - return self.input_fields[0].item_value() - - output = {} - for input_field in self.input_fields: - output.update(input_field.config_value()) - return output - - TypeToKlass.types["boolean"] = BooleanWidget -TypeToKlass.types["text-singleline"] = TextSingleLineWidget -TypeToKlass.types["path-input"] = PathInputWidget -TypeToKlass.types["path-widget"] = PathWidget -TypeToKlass.types["text-multiline"] = TextMultiLineWidget -TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["number"] = NumberWidget +TypeToKlass.types["text-singleline"] = TextSingleLineWidget +TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["path-input"] = PathInputWidget +TypeToKlass.types["raw-json"] = RawJsonWidget +TypeToKlass.types["list"] = ListWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict TypeToKlass.types["dict"] = DictWidget -TypeToKlass.types["dict-form"] = DictFormWidget TypeToKlass.types["dict-invisible"] = DictInvisible -TypeToKlass.types["list"] = ListWidget +TypeToKlass.types["path-widget"] = PathWidget +TypeToKlass.types["dict-form"] = DictFormWidget From b183564954bd29735a85630ef2246fec0f1f3282 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 16:52:13 +0200 Subject: [PATCH 678/947] text input has one input widget with settable attribute "multiline" --- .../1_ftrack_projects_gui_schema.json | 6 +- .../projects_schema/1_plugins_gui_schema.json | 42 +++--- .../studio_schema/1_intents_gui_schema.json | 4 +- .../studio_schema/1_tray_items.json | 2 +- .../config_setting/widgets/inputs.py | 128 +++--------------- 5 files changed, 45 insertions(+), 137 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json index 51c05aeb3a..6608463100 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json @@ -14,11 +14,11 @@ "children": [ { "key": "Ready", - "type": "text-singleline", + "type": "text", "label": "Ready" }, { "key": "Ready2", - "type": "text-singleline", + "type": "text", "label": "Ready2" } ] @@ -32,7 +32,7 @@ "children": [ { "key": "in progress", - "type": "text-singleline", + "type": "text", "label": "In Progress" } ] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index e43518989a..98fbfb206d 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -34,7 +34,7 @@ "type": "dict-form", "children": [ { - "type": "text-singleline", + "type": "text", "key": "deadline_department", "label": "Deadline apartment", "default": "" @@ -44,17 +44,17 @@ "label": "Deadline priority", "default": 50 }, { - "type": "text-singleline", + "type": "text", "key": "deadline_pool", "label": "Deadline pool", "default": "" }, { - "type": "text-singleline", + "type": "text", "key": "deadline_pool_secondary", "label": "Deadline pool (secondary)", "default": "" }, { - "type": "text-singleline", + "type": "text", "key": "deadline_group", "label": "Deadline Group", "default": "" @@ -98,13 +98,13 @@ "label": "Enabled", "default": false }, { - "type": "text-singleline", + "type": "text", "key": "note_with_intent_template", "label": "Note with intent template", "default": "{intent}: {comment}" }, { "type": "list", - "object_type": "text-singleline", + "object_type": "text", "key": "note_labels", "label": "Note labels", "default": [] @@ -155,13 +155,13 @@ "children": [ { "type": "list", - "object_type": "text-singleline", + "object_type": "text", "key": "input", "label": "FFmpeg input arguments", "default": [] }, { "type": "list", - "object_type": "text-singleline", + "object_type": "text", "key": "output", "label": "FFmpeg output arguments", "default": [] @@ -271,15 +271,15 @@ "key": "enabled", "label": "Enabled" }, { - "type": "text-singleline", + "type": "text", "key": "deadline_department", "label": "Deadline department" }, { - "type": "text-singleline", + "type": "text", "key": "deadline_pool", "label": "Deadline Pool" }, { - "type": "text-singleline", + "type": "text", "key": "deadline_group", "label": "Deadline Group" } @@ -314,11 +314,11 @@ "key": "enabled", "label": "Enabled" }, { - "type": "text-singleline", + "type": "text", "key": "material_file", "label": "Material File" }, { - "type": "text-singleline", + "type": "text", "key": "regex", "label": "Validation regex", "default": "(.*)_(\\d)*_(?P.*)_(GEO)" @@ -351,7 +351,7 @@ "key": "enabled", "label": "Enabled" }, { - "type": "text-singleline", + "type": "text", "key": "regex", "label": "Validation regex", "default": "(?P.*)_(.*)_SHD" @@ -401,7 +401,7 @@ "is_group": true, "children": [ { - "type": "text-singleline", + "type": "text", "key": "fpath_template", "label": "Path template", "default": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" @@ -415,7 +415,7 @@ "is_group": true, "children": [ { - "type": "text-singleline", + "type": "text", "key": "fpath_template", "label": "Path template", "default": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" @@ -530,12 +530,12 @@ "label": "deadline_priority", "default": 50 }, { - "type": "text-singleline", + "type": "text", "key": "deadline_pool", "label": "deadline_pool", "default": "" }, { - "type": "text-singleline", + "type": "text", "key": "deadline_pool_secondary", "label": "deadline_pool_secondary", "default": "" @@ -598,7 +598,7 @@ "default": true }, { "type": "list", - "object_type": "text-singleline", + "object_type": "text", "key": "tags_addition", "label": "Tags addition", "default": [] @@ -629,12 +629,12 @@ "is_group": true, "children": [ { - "type": "text-singleline", + "type": "text", "key": "clipName", "label": "Clip name template", "default": "{track}{sequence}{shot}" }, { - "type": "text-singleline", + "type": "text", "key": "folder", "label": "Folder", "default": "takes" diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json index 18f3d42ba7..a4b5e16fa1 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json @@ -8,11 +8,11 @@ "children": [ { "type": "dict-modifiable", - "object_type": "text-singleline", + "object_type": "text", "key": "items", "label": "Intent Key/Label" }, { - "type": "text-singleline", + "type": "text", "key": "default", "label": "Default intent" } diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json index 610430b25e..13ab0293de 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json @@ -113,7 +113,7 @@ "expandable": true, "children": [ { - "type": "text-singleline", + "type": "text", "key": "workspace_name", "label": "Workspace name" } diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ac08a9ae4f..ce14e575cf 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -529,13 +529,13 @@ class NumberWidget(QtWidgets.QWidget, InputObject): return self.input_field.value() -class TextSingleLineWidget(QtWidgets.QWidget, InputObject): +class TextWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( self, input_data, parent, as_widget=False, label_widget=None ): - super(TextSingleLineWidget, self).__init__(parent) + super(TextWidget, self).__init__(parent) self._parent = parent self._as_widget = as_widget @@ -545,6 +545,8 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) + self.multiline = input_data.get("multiline", False) + self.override_value = NOT_SET self.global_value = NOT_SET self.start_value = NOT_SET @@ -553,7 +555,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.text_input = QtWidgets.QLineEdit(self) + if self.multiline: + self.text_input = QtWidgets.QPlainTextEdit(self) + else: + self.text_input = QtWidgets.QLineEdit(self) self.setFocusProxy(self.text_input) @@ -576,10 +581,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): value = parent_values.get(self.key, NOT_SET) if value is not NOT_SET: - self.text_input.setText(value) + self.set_value(value) elif self.default_value is not NOT_SET: - self.text_input.setText(self.default_value) + self.set_value(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -587,7 +592,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value def set_value(self, value, *, global_value=False): - self.text_input.setText(value) + if self.multiline: + self.text_input.setPlainText(value) + else: + self.text_input.setText(value) if global_value: self.start_value = self.item_value() self.global_value = self.item_value() @@ -629,109 +637,10 @@ class TextSingleLineWidget(QtWidgets.QWidget, InputObject): widget.style().polish(widget) def item_value(self): - return self.text_input.text() - - -class TextMultiLineWidget(QtWidgets.QWidget, InputObject): - value_changed = QtCore.Signal(object) - - def __init__( - self, input_data, parent, as_widget=False, label_widget=None - ): - super(TextMultiLineWidget, self).__init__(parent) - - self._parent = parent - self._as_widget = as_widget - self._state = None - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) - - self.override_value = NOT_SET - self.global_value = NOT_SET - self.start_value = NOT_SET - - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - - self.text_input = QtWidgets.QPlainTextEdit(self) - - self.setFocusProxy(self.text_input) - - if not self._as_widget: - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - self.label_widget = label_widget - layout.addWidget(self.text_input, 1) - - self.text_input.textChanged.connect(self._on_value_change) - - def update_global_values(self, parent_values): - value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.text_input.setPlainText(value) - - elif self.default_value is not NOT_SET: - self.text_input.setPlainText(self.default_value) - - self.global_value = value - self.start_value = self.item_value() - - self._is_modified = self.global_value != self.start_value - - def set_value(self, value, *, global_value=False): - self.text_input.setPlainText(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() - - def reset_value(self): - self.set_value(self.start_value) - - def clear_value(self): - self.set_value("") - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self._is_modified = self.item_value() != self.global_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() - - self.value_changed.emit(self) - - def update_style(self): - state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.text_input + if self.multiline: + return self.text_input.toPlainText() else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - - def item_value(self): - return self.text_input.toPlainText() + return self.text_input.text() class PathInputWidget(QtWidgets.QWidget, InputObject): @@ -2364,8 +2273,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["number"] = NumberWidget -TypeToKlass.types["text-singleline"] = TextSingleLineWidget -TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["text"] = TextWidget TypeToKlass.types["path-input"] = PathInputWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["list"] = ListWidget From 5cba39b998bbb563ebbeb59cac415b87f9498ce0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 17:16:08 +0200 Subject: [PATCH 679/947] path widget works right with overrides --- .../config_setting/widgets/inputs.py | 125 ++++++++++++++++-- 1 file changed, 115 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ce14e575cf..09e4629a45 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1937,7 +1937,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): return {self.key: values}, self.is_group -class PathWidget(QtWidgets.QWidget, InputObject): +class PathWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) platforms = ("windows", "darwin", "linux") @@ -1958,8 +1958,19 @@ class PathWidget(QtWidgets.QWidget, InputObject): super(PathWidget, self).__init__(parent) self._parent = parent + self._state = None + self._child_state = None - self._is_group = input_data.get("is_group", False) + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + self.any_parent_is_group = any_parent_is_group + + # This is partial input and dictionary input + if not any_parent_is_group: + self._is_group = True + else: + self._is_group = False self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) @@ -1970,8 +1981,6 @@ class PathWidget(QtWidgets.QWidget, InputObject): self.global_value = NOT_SET self.start_value = NOT_SET - self._state = None - self.input_fields = [] if not self.multiplatform and not self.multipath: @@ -2062,6 +2071,30 @@ class PathWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + self._is_modified = False + override_values = NOT_SET + if parent_values is not NOT_SET: + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = override_values is not NOT_SET + if not self.multiplatform: + self.input_fields[0].apply_overrides(parent_values) + else: + for item in self.input_fields: + item.apply_overrides(override_values) + + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden + ) + self._was_overriden = bool(self._is_overriden) + def set_value(self, value, *, global_value=False): if not self.multiplatform: self.input_fields[0].set_value(value, global_value=global_value) @@ -2089,16 +2122,79 @@ class PathWidget(QtWidgets.QWidget, InputObject): return self._is_modified = self.item_value() != self.global_value - if self.is_overidable: - self._is_overriden = True - - self.update_style() + if self.is_group: + if self.is_overidable: + self._is_overriden = True + self.hierarchical_style_update() self.value_changed.emit(self) - def update_style(self): + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_invalid, self.child_overriden, child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + state = self.style_state( + child_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + for item in self.input_fields: + item.remove_overrides() + + def discard_changes(self): for input_field in self.input_fields: - input_field.update_style() + input_field.discard_changes() + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() def item_value(self): if not self.multiplatform and not self.multipath: @@ -2112,6 +2208,15 @@ class PathWidget(QtWidgets.QWidget, InputObject): output.update(input_field.config_value()) return output + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + value = self.item_value() + if not self.multiplatform: + value = {self.key: value} + return value, self.is_group + # Proxy for form layout class FormLabel(QtWidgets.QLabel): From cbf1519645ff0e200af6e464314a8dbe276fea14 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 19:10:43 +0200 Subject: [PATCH 680/947] first introducing enhancement of anatomy widget --- .../config_setting/widgets/anatomy_inputs.py | 74 +++++++++++++++++-- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 7c9ac0d8f4..d07f69e7ca 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -5,28 +5,59 @@ from .lib import NOT_SET, TypeToKlass class AnatomyWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) + template_keys = ( + "project[name]", + "project[code]", + "asset", + "task", + "subset", + "family", + "version", + "ext", + "representation" + ) + default_exmaple_data = { + "project": { + "name": "ProjectPype", + "code": "pp", + }, + "asset": "sq01sh0010", + "task": "compositing", + "subset": "renderMain", + "family": "render", + "version": 1, + "ext": ".png", + "representation": "png" + } def __init__( self, input_data, parent, as_widget=False, label_widget=None ): + super(AnatomyWidget, self).__init__(parent) + self._parent = parent self._as_widget = as_widget self._is_group = True - self._state = None self.key = "anatomy" - self.start_value = None - super(AnatomyWidget, self).__init__(parent) + self.override_value = NOT_SET + self.start_value = NOT_SET + self.global_value = NOT_SET + + self.root_keys = None + self.root_widget = RootsWidget(self) + self.templates_widget = TemplatesWidget(self) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - label = QtWidgets.QLabel("Test") - layout.addWidget(label) - self.override_value = NOT_SET + label = QtWidgets.QLabel("Anatomy", self) + layout.addWidget(label) + layout.addWidget(self.root_widget) + layout.addWidget(self.templates_widget) def update_global_values(self, values): print("* update_global_values") @@ -47,10 +78,37 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): print("* item_value") -class TemplatesWidget: - pass +class RootsWidget(QtWidgets.QWidget): + multiroot_changed = QtCore.QSignal() + + def __init__(self, parent=None): + super(RootsWidget, self).__init__(parent) + + self.root_keys = None + + layout = QtWidgets.QHBoxLayout(self) + multiroot_checkbox = QtWidgets.QCheckBox(self) + layout.addWidget(multiroot_checkbox) + + self.multiroot_checkbox = multiroot_checkbox + multiroot_checkbox.stateChanged.connect(self._on_multiroot_checkbox) + + def _on_multiroot_checkbox(self): + self.set_multiroot(self.multiroot_checkbox.isChecked()) + + def set_multiroot(self, is_multiroot=None): + if is_multiroot is None: + is_multiroot = not self.multiroot_checkbox.isChecked() + + if is_multiroot != self.multiroot_checkbox.isChecked(): + self.multiroot_checkbox.setChecked(is_multiroot) + + self.multiroot_changed.emit() +class TemplatesWidget(QtWidgets.QWidget): + def __init__(self, parent=None): + super(TemplatesWidget, self).__init__(parent) TypeToKlass.types["anatomy"] = AnatomyWidget From 059152188d191240791e9b53f862bb17c452e7d8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 19:25:34 +0200 Subject: [PATCH 681/947] fix numer value when decimals are set to 0 --- .../tools/config_setting/config_setting/widgets/widgets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index d5b0f088de..35b515d641 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -18,6 +18,13 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox): else: event.ignore() + def value(self): + output = super(NumberSpinBox, self).value() + print(self.decimals()) + if self.decimals() == 0: + output = int(output) + return output + class PathInput(QtWidgets.QLineEdit): def clear_end_path(self): From 043aec3fef57a430bbed0cd9bede58f466758518 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 2 Sep 2020 19:25:47 +0200 Subject: [PATCH 682/947] fix class name in anatomy --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index d07f69e7ca..46b69aaffe 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -79,7 +79,7 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): class RootsWidget(QtWidgets.QWidget): - multiroot_changed = QtCore.QSignal() + multiroot_changed = QtCore.Signal() def __init__(self, parent=None): super(RootsWidget, self).__init__(parent) From a88fcfe9fa3de9e2b52478b70f1ddb0b8a72f7c2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 11:19:57 +0200 Subject: [PATCH 683/947] removed debug print --- pype/tools/config_setting/config_setting/widgets/widgets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 35b515d641..e9ba1b798c 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -20,7 +20,6 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox): def value(self): output = super(NumberSpinBox, self).value() - print(self.decimals()) if self.decimals() == 0: output = int(output) return output From bccaec878ff084bafe8c04cb604b34f2d377f08b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 11:46:11 +0200 Subject: [PATCH 684/947] PathWidget can be as widget --- .../config_setting/widgets/inputs.py | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 09e4629a45..c0c0ba3406 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1960,6 +1960,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self._parent = parent self._state = None self._child_state = None + self._as_widget = as_widget any_parent_is_group = parent.is_group if not any_parent_is_group: @@ -1967,7 +1968,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group # This is partial input and dictionary input - if not any_parent_is_group: + if not any_parent_is_group and not as_widget: self._is_group = True else: self._is_group = False @@ -1990,12 +1991,14 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.key = input_data["key"] - if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) - self.label_widget = label_widget + if not as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget self.content_widget = QtWidgets.QWidget(self) self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) @@ -2056,15 +2059,16 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): def update_global_values(self, parent_values): value = NOT_SET - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if not self._as_widget: + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if not self.multiplatform: - self.input_fields[0].update_global_values(parent_values) + if not self.multiplatform: + self.input_fields[0].update_global_values(parent_values) - elif self.multiplatform: - for input_field in self.input_fields: - input_field.update_global_values(value) + elif self.multiplatform: + for input_field in self.input_fields: + input_field.update_global_values(value) self.global_value = value self.start_value = self.item_value() From e515895cf38e1c4f73a740dfa4f3c49ff8c80efe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 11:46:40 +0200 Subject: [PATCH 685/947] roots widget has multiroot and singleroot widget --- .../config_setting/widgets/anatomy_inputs.py | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 46b69aaffe..3148b8f32d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,5 +1,5 @@ from Qt import QtWidgets, QtCore -from .inputs import InputObject +from .inputs import ConfigObject, InputObject, ModifiableDict, PathWidget from .lib import NOT_SET, TypeToKlass @@ -50,7 +50,7 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): self.root_widget = RootsWidget(self) self.templates_widget = TemplatesWidget(self) - layout = QtWidgets.QHBoxLayout(self) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) @@ -61,6 +61,8 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): def update_global_values(self, values): print("* update_global_values") + self.root_widget.update_global_values(values) + self.templates_widget.update_global_values(values) def set_value(self, value, *, global_value=False): print("* set_value") @@ -78,21 +80,59 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): print("* item_value") -class RootsWidget(QtWidgets.QWidget): +class RootsWidget(QtWidgets.QWidget, ConfigObject): multiroot_changed = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, parent): super(RootsWidget, self).__init__(parent) + self._parent = parent + self._is_group = True self.root_keys = None - layout = QtWidgets.QHBoxLayout(self) - multiroot_checkbox = QtWidgets.QCheckBox(self) - layout.addWidget(multiroot_checkbox) + checkbox_widget = QtWidgets.QWidget(self) + + multiroot_label = QtWidgets.QLabel( + "Use multiple roots", checkbox_widget + ) + multiroot_checkbox = QtWidgets.QCheckBox(checkbox_widget) + + checkbox_layout = QtWidgets.QHBoxLayout(checkbox_widget) + checkbox_layout.addWidget(multiroot_label, 0) + checkbox_layout.addWidget(multiroot_checkbox, 1) + + path_widget_data = { + "key": "roots", + "multiplatform": True, + "label": "Roots" + } + singleroot_widget = PathWidget(path_widget_data, self) + multiroot_data = { + "key": "roots", + "label": "Roots", + "object_type": "path-widget", + "input_modifiers": { + "multiplatform": True + } + } + multiroot_widget = ModifiableDict(multiroot_data, self) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(checkbox_widget) + main_layout.addWidget(singleroot_widget) + main_layout.addWidget(multiroot_widget) self.multiroot_checkbox = multiroot_checkbox + self.singleroot_widget = singleroot_widget + self.multiroot_widget = multiroot_widget + multiroot_checkbox.stateChanged.connect(self._on_multiroot_checkbox) + def update_global_values(self, values): + print(values) + self.singleroot_widget.update_global_values(NOT_SET) + self.multiroot_widget.update_global_values(NOT_SET) + def _on_multiroot_checkbox(self): self.set_multiroot(self.multiroot_checkbox.isChecked()) @@ -110,5 +150,8 @@ class TemplatesWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(TemplatesWidget, self).__init__(parent) + def update_global_values(self, values): + pass + TypeToKlass.types["anatomy"] = AnatomyWidget From f147579ad943baf56c4da157aea2b8cada39fbf0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 11:56:00 +0200 Subject: [PATCH 686/947] pass global values to subwidgets --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 3148b8f32d..a0c94fb846 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -129,9 +129,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): multiroot_checkbox.stateChanged.connect(self._on_multiroot_checkbox) def update_global_values(self, values): - print(values) - self.singleroot_widget.update_global_values(NOT_SET) - self.multiroot_widget.update_global_values(NOT_SET) + self.singleroot_widget.update_global_values(values) + self.multiroot_widget.update_global_values(values) def _on_multiroot_checkbox(self): self.set_multiroot(self.multiroot_checkbox.isChecked()) From 462c350c0bdc25dff4700780a3d289f0c12f2e80 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 11:57:05 +0200 Subject: [PATCH 687/947] modifiable dict has buttons at first place --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c0c0ba3406..36bb8c9902 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1228,10 +1228,10 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.add_btn.setProperty("btn-type", "tool-item") self.remove_btn.setProperty("btn-type", "tool-item") - layout.addWidget(self.key_input, 0) - layout.addWidget(self.value_input, 1) layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) + layout.addWidget(self.key_input, 0) + layout.addWidget(self.value_input, 1) self.setFocusProxy(self.value_input) From 93a0905b6f8be0f4607a8169a0c2ddf717a40d6d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 12:11:58 +0200 Subject: [PATCH 688/947] ModifiedDictionary Item looks same as ListItem --- .../config_setting/widgets/inputs.py | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 36bb8c9902..441bc6c9a5 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1263,6 +1263,21 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def is_group(self): return self._parent.is_group + def on_add_clicked(self): + if self.value_input.isEnabled(): + self._parent.add_row(row=self.row() + 1) + else: + self.set_as_empty(False) + + def on_remove_clicked(self): + self._parent.remove_row(self) + + def set_as_empty(self, is_empty=True): + self.key_input.setEnabled(not is_empty) + self.value_input.setEnabled(not is_empty) + self.remove_btn.setEnabled(not is_empty) + self._on_value_change() + @property def any_parent_is_group(self): return self._parent.any_parent_is_group @@ -1289,16 +1304,6 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def row(self): return self._parent.input_fields.index(self) - def on_add_clicked(self): - self._parent.add_row(row=self.row() + 1) - - def on_remove_clicked(self): - if self.is_single: - self.value_input.clear_value() - self.key_input.setText("") - else: - self._parent.remove_row(self) - def config_value(self): key = self.key_input.text() value = self.value_input.item_value() @@ -1374,12 +1379,12 @@ class ModifiableDict(ExpandingWidget, InputObject): for item_key, item_value in self.default_value.items(): self.add_row(key=item_key, value=item_value) - if self.count() == 0: - self.add_row() - for old_input in old_inputs: self.remove_row(old_input) + if self.count() == 0: + self.add_row(is_empty=True) + self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value @@ -1441,19 +1446,13 @@ class ModifiableDict(ExpandingWidget, InputObject): output.update(item_value) return output - def add_row(self, row=None, key=None, value=None): + def add_row(self, row=None, key=None, value=None, is_empty=False): # Create new item item_widget = ModifiableDictItem( self.object_type, self.input_modifiers, self, self.inputs_widget ) - - # Set/unset if new item is single item - current_count = self.count() - if current_count == 0: - item_widget.is_single = True - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = False + if is_empty: + item_widget.set_as_empty() item_widget.value_changed.connect(self._on_value_change) @@ -1493,12 +1492,8 @@ class ModifiableDict(ExpandingWidget, InputObject): item_widget.setParent(None) item_widget.deleteLater() - current_count = self.count() - if current_count == 0: - self.add_row() - elif current_count == 1: - for _input_field in self.input_fields: - _input_field.is_single = True + if self.count() == 0: + self.add_row(is_empty=True) self._on_value_change() self.parent().updateGeometry() From 787d8516cbaa288b3c8c14c83c0f2fd53cfa52da Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 13:21:11 +0200 Subject: [PATCH 689/947] hide inputs based on checkbox --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index a0c94fb846..943b8b0973 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -128,6 +128,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): multiroot_checkbox.stateChanged.connect(self._on_multiroot_checkbox) + self._on_multiroot_checkbox() + def update_global_values(self, values): self.singleroot_widget.update_global_values(values) self.multiroot_widget.update_global_values(values) @@ -142,6 +144,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot != self.multiroot_checkbox.isChecked(): self.multiroot_checkbox.setChecked(is_multiroot) + self.singleroot_widget.setVisible(not is_multiroot) + self.multiroot_widget.setVisible(is_multiroot) + self.multiroot_changed.emit() From f505f3099ac79d9bdde4fd9c952576eb5672afc0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 13:53:00 +0200 Subject: [PATCH 690/947] modifiable dict is not based on expanding widget but using it inside --- .../config_setting/widgets/inputs.py | 56 ++++++++++++------- .../config_setting/widgets/widgets.py | 2 +- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 441bc6c9a5..394a0f69ca 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1312,7 +1312,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): return {key: value} -class ModifiableDict(ExpandingWidget, InputObject): +class ModifiableDict(QtWidgets.QWidget, InputObject): # Should be used only for dictionary with one datatype as value # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) @@ -1320,7 +1320,7 @@ class ModifiableDict(ExpandingWidget, InputObject): def __init__( self, input_data, parent, as_widget=False, label_widget=None ): - super(ModifiableDict, self).__init__(input_data["label"], parent) + super(ModifiableDict, self).__init__(parent) self.setObjectName("ModifiableDict") self._parent = parent @@ -1330,10 +1330,6 @@ class ModifiableDict(ExpandingWidget, InputObject): self.global_value = NOT_SET self.start_value = NOT_SET - self.key = input_data["key"] - - self.input_fields = [] - any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group @@ -1343,17 +1339,39 @@ class ModifiableDict(ExpandingWidget, InputObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) - inputs_widget = QtWidgets.QWidget(self) - inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.input_fields = [] - inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) - inputs_layout.setContentsMargins(5, 5, 0, 5) - inputs_layout.setSpacing(5) + self.key = input_data["key"] - self.set_content_widget(inputs_widget) + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(5, 5, 0, 5) + main_layout.setSpacing(0) - self.inputs_widget = inputs_widget - self.inputs_layout = inputs_layout + body_widget = ExpandingWidget(input_data["label"], self) + + main_layout.addWidget(body_widget) + + content_widget = QtWidgets.QWidget(self) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 0, 3) + + body_widget.set_content_widget(content_widget) + + self.body_widget = body_widget + self.content_widget = content_widget + self.content_layout = content_layout + + self.label_widget = body_widget.label_widget + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + expandable = input_data.get("expandable", True) + if not expandable: + body_widget.hide_toolbox(hide_content=False) + else: + expanded = input_data.get("expanded", False) + if expanded: + body_widget.toggle_content() self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) @@ -1449,7 +1467,7 @@ class ModifiableDict(ExpandingWidget, InputObject): def add_row(self, row=None, key=None, value=None, is_empty=False): # Create new item item_widget = ModifiableDictItem( - self.object_type, self.input_modifiers, self, self.inputs_widget + self.object_type, self.input_modifiers, self, self.content_widget ) if is_empty: item_widget.set_as_empty() @@ -1457,10 +1475,10 @@ class ModifiableDict(ExpandingWidget, InputObject): item_widget.value_changed.connect(self._on_value_change) if row is None: - self.inputs_layout.addWidget(item_widget) + self.content_layout.addWidget(item_widget) self.input_fields.append(item_widget) else: - self.inputs_layout.insertWidget(row, item_widget) + self.content_layout.insertWidget(row, item_widget) self.input_fields.insert(row, item_widget) previous_input = None @@ -1487,7 +1505,7 @@ class ModifiableDict(ExpandingWidget, InputObject): def remove_row(self, item_widget): item_widget.value_changed.disconnect() - self.inputs_layout.removeWidget(item_widget) + self.content_layout.removeWidget(item_widget) self.input_fields.remove(item_widget) item_widget.setParent(None) item_widget.deleteLater() @@ -1543,7 +1561,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 0, 3) - body_widget.set_content_widget(content_widget, (4, 4, 0, 4)) + body_widget.set_content_widget(content_widget) self.body_widget = body_widget self.content_widget = content_widget diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index e9ba1b798c..fbbc3f26df 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -111,7 +111,7 @@ class ExpandingWidget(QtWidgets.QWidget): def set_content_widget(self, content_widget, margins=None): main_layout = QtWidgets.QVBoxLayout(self) if margins is None: - margins = (9, 9, 0, 9) + margins = (4, 4, 0, 4) main_layout.setContentsMargins(*margins) content_widget.setVisible(False) From 88e2a794d4d79eb6c4a57d922c7b6e1614e7e7e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 16:23:46 +0200 Subject: [PATCH 691/947] added abstract class for objects --- .../config_setting/widgets/inputs.py | 76 +------- .../config_setting/widgets/widgets.py | 180 ++++++++++++++++++ 2 files changed, 185 insertions(+), 71 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 394a0f69ca..379b255359 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2,6 +2,7 @@ import json import logging from Qt import QtWidgets, QtCore, QtGui from .widgets import ( + AbstractConfigObject, ExpandingWidget, NumberSpinBox, PathInput @@ -9,7 +10,7 @@ from .widgets import ( from .lib import NOT_SET, METADATA_KEY, TypeToKlass -class ConfigObject: +class ConfigObject(AbstractConfigObject): allow_actions = True default_state = "" @@ -72,10 +73,6 @@ class ConfigObject: @property def ignore_value_changes(self): """Most of attribute changes are ignored on value change when True.""" - if not hasattr(self, "_parent"): - raise NotImplementedError( - "Object {} does not have `_parent` attribute".format(self) - ) return self._parent.ignore_value_changes @ignore_value_changes.setter @@ -83,44 +80,12 @@ class ConfigObject: """Setter for global parent item to apply changes for all inputs.""" self._parent.ignore_value_changes = value - @property - def child_modified(self): - """Any children item is modified.""" - raise NotImplementedError( - "{} does not have implemented `child_modified`".format(self) - ) - - @property - def child_overriden(self): - """Any children item is overriden.""" - raise NotImplementedError( - "{} does not have implemented `child_overriden`".format(self) - ) - - @property - def child_invalid(self): - """Any children item does not have valid value.""" - raise NotImplementedError( - "{} does not have implemented `child_invalid`".format(self) - ) - - def get_invalid(self): - """Returns invalid items all down the hierarchy.""" - raise NotImplementedError( - "{} does not have implemented `get_invalid`".format(self) - ) - - def item_value(self): - """Value of an item without key.""" - raise NotImplementedError( - "Method `item_value` not implemented!" - ) - def config_value(self): """Output for saving changes or overrides.""" return {self.key: self.item_value()} - def style_state(self, is_invalid, is_overriden, is_modified): + @classmethod + def style_state(cls, is_invalid, is_overriden, is_modified): items = [] if is_invalid: items.append("invalid") @@ -129,54 +94,23 @@ class ConfigObject: items.append("overriden") if is_modified: items.append("modified") - return "-".join(items) or self.default_state - - def add_children_gui(self, child_configuration, values): - raise NotImplementedError( - "{} Method `add_children_gui` is not implemented!.".format( - repr(self) - ) - ) + return "-".join(items) or cls.default_state def _discard_changes(self): self.ignore_value_changes = True self.discard_changes() self.ignore_value_changes = False - def discard_changes(self): - raise NotImplementedError( - "{} Method `discard_changes` not implemented!".format( - repr(self) - ) - ) - def _remove_overrides(self): self.ignore_value_changes = True self.remove_overrides() self.ignore_value_changes = False - def remove_overrides(self): - raise NotImplementedError( - "{} Method `remove_overrides` not implemented!".format( - repr(self) - ) - ) - def _set_as_overriden(self): self.ignore_value_changes = True self.set_as_overriden() self.ignore_value_changes = False - def set_as_overriden(self): - raise NotImplementedError( - "Method `set_as_overriden` not implemented!" - ) - - def hierarchical_style_update(self): - raise NotImplementedError( - "Method `hierarchical_style_update` not implemented!" - ) - def mouseReleaseEvent(self, event): if self.allow_actions and event.button() == QtCore.Qt.RightButton: menu = QtWidgets.QMenu() diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index fbbc3f26df..42ca49d600 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -180,3 +180,183 @@ class UnsavedChangesDialog(QtWidgets.QDialog): def on_discard_pressed(self): self.done(2) + + +class AbstractConfigObject: + abstract_attributes = ("_parent", ) + + def __getattr__(self, name): + if name in self.abstract_attributes: + raise NotImplementedError( + "Attribute `{}` is not implemented. {}".format(name, self) + ) + return super(AbstractConfigObject, self).__getattribute__(name) + + @property + def log(self): + raise NotImplementedError( + "{} does not have implemented `log`".format(self) + ) + + @property + def is_modified(self): + """Has object any changes that require saving.""" + raise NotImplementedError( + "{} does not have implemented `is_modified`".format(self) + ) + + @property + def is_overriden(self): + """Is object overriden so should be saved to overrides.""" + raise NotImplementedError( + "{} does not have implemented `is_overriden`".format(self) + ) + + @property + def was_overriden(self): + """Initial state after applying overrides.""" + raise NotImplementedError( + "{} does not have implemented `was_overriden`".format(self) + ) + + @property + def is_invalid(self): + """Value set in is not valid.""" + raise NotImplementedError( + "{} does not have implemented `is_invalid`".format(self) + ) + + @property + def is_group(self): + """Value set in is not valid.""" + raise NotImplementedError( + "{} does not have implemented `is_group`".format(self) + ) + + @property + def is_nullable(self): + raise NotImplementedError( + "{} does not have implemented `is_nullable`".format(self) + ) + + @property + def is_overidable(self): + """Should care about overrides.""" + raise NotImplementedError( + "{} does not have implemented `is_overidable`".format(self) + ) + + def any_parent_overriden(self): + """Any of parent object up to top hiearchy is overriden.""" + raise NotImplementedError( + "{} does not have implemented `any_parent_overriden`".format(self) + ) + + @property + def ignore_value_changes(self): + """Most of attribute changes are ignored on value change when True.""" + raise NotImplementedError( + "{} does not have implemented `ignore_value_changes`".format(self) + ) + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + """Setter for global parent item to apply changes for all inputs.""" + raise NotImplementedError(( + "{} does not have implemented setter method `ignore_value_changes`" + ).format(self)) + + @property + def child_modified(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_modified`".format(self) + ) + + @property + def child_overriden(self): + """Any children item is overriden.""" + raise NotImplementedError( + "{} does not have implemented `child_overriden`".format(self) + ) + + @property + def child_invalid(self): + """Any children item does not have valid value.""" + raise NotImplementedError( + "{} does not have implemented `child_invalid`".format(self) + ) + + def get_invalid(self): + """Returns invalid items all down the hierarchy.""" + raise NotImplementedError( + "{} does not have implemented `get_invalid`".format(self) + ) + + def item_value(self): + """Value of an item without key.""" + raise NotImplementedError( + "Method `item_value` not implemented!" + ) + + def config_value(self): + """Output for saving changes or overrides.""" + return {self.key: self.item_value()} + + @classmethod + def style_state(cls, is_invalid, is_overriden, is_modified): + items = [] + if is_invalid: + items.append("invalid") + else: + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or cls.default_state + + def add_children_gui(self, child_configuration, values): + raise NotImplementedError( + "{} Method `add_children_gui` is not implemented!.".format( + repr(self) + ) + ) + + def _discard_changes(self): + self.ignore_value_changes = True + self.discard_changes() + self.ignore_value_changes = False + + def discard_changes(self): + raise NotImplementedError( + "{} Method `discard_changes` not implemented!".format( + repr(self) + ) + ) + + def _remove_overrides(self): + self.ignore_value_changes = True + self.remove_overrides() + self.ignore_value_changes = False + + def remove_overrides(self): + raise NotImplementedError( + "{} Method `remove_overrides` not implemented!".format( + repr(self) + ) + ) + + def _set_as_overriden(self): + self.ignore_value_changes = True + self.set_as_overriden() + self.ignore_value_changes = False + + def set_as_overriden(self): + raise NotImplementedError( + "Method `set_as_overriden` not implemented!" + ) + + def hierarchical_style_update(self): + raise NotImplementedError( + "Method `hierarchical_style_update` not implemented!" + ) From 3ef141c8cadcd77caba894517764ff9730750716 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 3 Sep 2020 16:24:08 +0200 Subject: [PATCH 692/947] Anatmy widget updated style changes --- .../config_setting/style/style.css | 21 +-- .../config_setting/widgets/anatomy_inputs.py | 172 ++++++++++++++++-- 2 files changed, 167 insertions(+), 26 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 937259ba7b..638bf1c6fb 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -123,8 +123,7 @@ QPushButton[btn-type="expand-toggle"] { #DictLabel { font-weight: bold; } - -#ModifiableDict, #DictWidget { +#ModifiableDict, #DictWidget, #AnatomyWidget { border-style: solid; border-color: #455c6e; border-left-width: 3px; @@ -132,36 +131,36 @@ QPushButton[btn-type="expand-toggle"] { border-right-width: 0px; border-top-width: 0px; } -#ModifiableDict:hover, #DictWidget:hover { +#ModifiableDict:hover, #DictWidget:hover, #AnatomyWidget:hover { border-color: #62839d; } -#ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"] { +#ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"], #AnatomyWidget[state="child-modified"] { border-color: #106aa2; } -#ModifiableDict[state="child-modified"]:hover, #DictWidget[state="child-modified"]:hover { +#ModifiableDict[state="child-modified"]:hover, #DictWidget[state="child-modified"]:hover, #AnatomyWidget[state="child-modified"]:hover { border-color: #137cbd; } -#ModifiableDict[state="child-invalid"], #DictWidget[state="child-invalid"] { +#ModifiableDict[state="child-invalid"], #DictWidget[state="child-invalid"], #AnatomyWidget[state="child-invalid"] { border-color: #ad2e2e; } -#ModifiableDict[state="child-invalid"]:hover, #DictWidget[state="child-invalid"]:hover { +#ModifiableDict[state="child-invalid"]:hover, #DictWidget[state="child-invalid"]:hover, #AnatomyWidget[state="child-invalid"]:hover { border-color: #c93636; } -#ExpandingWidget[state="child-overriden"], #ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"] { +#ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"], #AnatomyWidget[state="child-overriden"] { border-color: #e67300; } -#ExpandingWidget[state="child-overriden"]:hover, #ModifiableDict[state="child-overriden"]:hover, #DictWidget[state="child-overriden"]:hover { +#ModifiableDict[state="child-overriden"]:hover, #DictWidget[state="child-overriden"]:hover, #AnatomyWidget[state="child-overriden"]:hover { border-color: #ff8c1a; } -#ExpandingWidget[state="child-overriden-modified"], #ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"] { +#ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"], #AnatomyWidget[state="child-modified"] { border-color: #106aa2; } -#ExpandingWidget[state="child-overriden-modified"]:hover, #ModifiableDict[state="child-overriden-modified"]:hover, #DictWidget[state="child-overriden-modified"]:hover { +#ModifiableDict[state="child-overriden-modified"]:hover, #DictWidget[state="child-overriden-modified"]:hover, #AnatomyWidget[state="child-modified"]:hover { border-color: #137cbd; } diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 943b8b0973..11d94319e6 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,9 +1,10 @@ from Qt import QtWidgets, QtCore -from .inputs import ConfigObject, InputObject, ModifiableDict, PathWidget +from .widgets import ExpandingWidget +from .inputs import ConfigObject, ModifiableDict, PathWidget from .lib import NOT_SET, TypeToKlass -class AnatomyWidget(QtWidgets.QWidget, InputObject): +class AnatomyWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) template_keys = ( "project[name]", @@ -33,10 +34,16 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): def __init__( self, input_data, parent, as_widget=False, label_widget=None ): + if as_widget: + raise TypeError( + "`AnatomyWidget` does not allow to be used as widget." + ) super(AnatomyWidget, self).__init__(parent) - + self.setObjectName("AnatomyWidget") self._parent = parent - self._as_widget = as_widget + + self._child_state = None + self._state = None self._is_group = True @@ -50,14 +57,26 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): self.root_widget = RootsWidget(self) self.templates_widget = TemplatesWidget(self) - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) + self.setAttribute(QtCore.Qt.WA_StyledBackground) - label = QtWidgets.QLabel("Anatomy", self) - layout.addWidget(label) - layout.addWidget(self.root_widget) - layout.addWidget(self.templates_widget) + body_widget = ExpandingWidget("Anatomy", self) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 0, 5) + layout.setSpacing(0) + layout.addWidget(body_widget) + + content_widget = QtWidgets.QWidget(body_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(5) + + content_layout.addWidget(self.root_widget) + content_layout.addWidget(self.templates_widget) + + body_widget.set_content_widget(content_widget) + + self.label_widget = body_widget.label_widget def update_global_values(self, values): print("* update_global_values") @@ -73,8 +92,63 @@ class AnatomyWidget(QtWidgets.QWidget, InputObject): def _on_value_change(self, item=None): print("* _on_value_change") - def update_style(self): + def update_style(self, is_overriden=None): print("* update_style") + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_invalid, self.child_overriden, child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + state = self.style_state( + child_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def hierarchical_style_update(self): + self.root_widget.hierarchical_style_update() + self.templates_widget.hierarchical_style_update() + self.update_style() + + @property + def is_modified(self): + return self._is_modified or self.child_modified + + @property + def child_modified(self): + return ( + self.root_widget.child_modified + or self.templates_widget.child_modified + ) + + @property + def child_overriden(self): + return ( + self.root_widget.is_overriden + or self.root_widget.child_overriden + or self.templates_widget.is_overriden + or self.templates_widget.child_overriden + ) + + @property + def child_invalid(self): + return ( + self.root_widget.child_invalid + or self.templates_widget.child_invalid + ) def item_value(self): print("* item_value") @@ -85,6 +159,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def __init__(self, parent): super(RootsWidget, self).__init__(parent) + self.setObjectName("RootsWidget") self._parent = parent self._is_group = True @@ -111,6 +186,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): "key": "roots", "label": "Roots", "object_type": "path-widget", + "expandable": False, "input_modifiers": { "multiplatform": True } @@ -130,18 +206,26 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._on_multiroot_checkbox() + @property + def is_multiroot(self): + return self.multiroot_checkbox.isChecked() + def update_global_values(self, values): self.singleroot_widget.update_global_values(values) self.multiroot_widget.update_global_values(values) + def hierarchical_style_update(self): + self.singleroot_widget.hierarchical_style_update() + self.multiroot_widget.hierarchical_style_update() + def _on_multiroot_checkbox(self): - self.set_multiroot(self.multiroot_checkbox.isChecked()) + self.set_multiroot(self.is_multiroot) def set_multiroot(self, is_multiroot=None): if is_multiroot is None: - is_multiroot = not self.multiroot_checkbox.isChecked() + is_multiroot = not self.is_multiroot - if is_multiroot != self.multiroot_checkbox.isChecked(): + if is_multiroot != self.is_multiroot: self.multiroot_checkbox.setChecked(is_multiroot) self.singleroot_widget.setVisible(not is_multiroot) @@ -149,6 +233,41 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.multiroot_changed.emit() + @property + def is_modified(self): + return self._is_modified or self.child_modified + + @property + def is_overriden(self): + return self._is_overriden + + @property + def child_modified(self): + if self.is_multiroot: + return self.multiroot_widget.child_modified + else: + return self.singleroot_widget.child_modified + + @property + def child_overriden(self): + if self.is_multiroot: + return ( + self.multiroot_widget.is_overriden + or self.multiroot_widget.child_overriden + ) + else: + return ( + self.singleroot_widget.is_overriden + or self.singleroot_widget.child_overriden + ) + + @property + def child_invalid(self): + if self.is_multiroot: + return self.multiroot_widget.child_invalid + else: + return self.singleroot_widget.child_invalid + class TemplatesWidget(QtWidgets.QWidget): def __init__(self, parent=None): @@ -157,5 +276,28 @@ class TemplatesWidget(QtWidgets.QWidget): def update_global_values(self, values): pass + def hierarchical_style_update(self): + pass + + @property + def is_modified(self): + return False + + @property + def is_overriden(self): + return False + + @property + def child_modified(self): + return False + + @property + def child_overriden(self): + return False + + @property + def child_invalid(self): + return False + TypeToKlass.types["anatomy"] = AnatomyWidget From 3271346b129f5b5027aec3d5343f771bbbe30a10 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 10:29:22 +0200 Subject: [PATCH 693/947] make sure saving won't crash if current files are not valid --- .../config_setting/config_setting/widgets/base.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index f057f5a0f6..066c00c96d 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -144,11 +144,14 @@ class StudioWidget(QtWidgets.QWidget): subpath = "/".join(key_sequence) + ".json" origin_values = current_configurations for key in key_sequence: - if key not in origin_values: + if not origin_values or key not in origin_values: origin_values = {} break origin_values = origin_values[key] + if not origin_values: + origin_values = {} + new_values = all_values for key in key_sequence: new_values = new_values[key] @@ -518,11 +521,14 @@ class ProjectWidget(QtWidgets.QWidget): subpath = "/".join(key_sequence) + ".json" origin_values = current_configurations for key in key_sequence: - if key not in origin_values: + if not origin_values or key not in origin_values: origin_values = {} break origin_values = origin_values[key] + if not origin_values: + origin_values = {} + new_values = all_values for key in key_sequence: new_values = new_values[key] From e87288998db354bae408af01fae131219f69a6d0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 10:30:03 +0200 Subject: [PATCH 694/947] pathwidget does not care about label if is used as widget --- .../config_setting/widgets/inputs.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 379b255359..f925e6e163 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2096,16 +2096,17 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.style().polish(self) self._child_state = child_state - state = self.style_state( - child_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return + if not self._as_widget: + state = self.style_state( + child_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) - self._state = state + self._state = state def remove_overrides(self): self._is_overriden = False From 3831af2a26d008f9c01ccd7b851b554adeb50e6d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 10:30:22 +0200 Subject: [PATCH 695/947] roots can collect data --- .../config_setting/widgets/anatomy_inputs.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 11d94319e6..c0a64a8d7d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -151,7 +151,12 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): ) def item_value(self): - print("* item_value") + output = {} + output.update(self.root_widget.config_value()) + return output + + def config_value(self): + return {self.key: self.item_value()} class RootsWidget(QtWidgets.QWidget, ConfigObject): @@ -162,11 +167,11 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.setObjectName("RootsWidget") self._parent = parent self._is_group = True + self.key = "roots" self.root_keys = None checkbox_widget = QtWidgets.QWidget(self) - multiroot_label = QtWidgets.QLabel( "Use multiple roots", checkbox_widget ) @@ -268,6 +273,15 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: return self.singleroot_widget.child_invalid + def item_value(self): + if self.is_multiroot: + return self.multiroot_widget.item_value() + else: + return self.singleroot_widget.item_value() + + def config_value(self): + return {self.key: self.item_value()} + class TemplatesWidget(QtWidgets.QWidget): def __init__(self, parent=None): From 69dbfd2faf101facbe611c57eec4d8f925d8d248 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 10:41:14 +0200 Subject: [PATCH 696/947] updating global values for roots should work --- .../config_setting/widgets/anatomy_inputs.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index c0a64a8d7d..e4f7a6383a 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -78,10 +78,14 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget - def update_global_values(self, values): - print("* update_global_values") - self.root_widget.update_global_values(values) - self.templates_widget.update_global_values(values) + def update_global_values(self, parent_values): + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + self.root_widget.update_global_values(value) + self.templates_widget.update_global_values(value) def set_value(self, value, *, global_value=False): print("* set_value") @@ -215,9 +219,25 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def is_multiroot(self): return self.multiroot_checkbox.isChecked() - def update_global_values(self, values): - self.singleroot_widget.update_global_values(values) - self.multiroot_widget.update_global_values(values) + def update_global_values(self, parent_values): + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + + self.set_multiroot(is_multiroot) + + if is_multiroot: + self.multiroot_widget.update_global_values(parent_values) + else: + self.singleroot_widget.update_global_values(parent_values) def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() From 7efc99b5ef87dbc9bc2b80fbeed3acb9f4970d63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 10:44:56 +0200 Subject: [PATCH 697/947] add first row to list and modifiable dictionary by default --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f925e6e163..ae0ece42bd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -982,6 +982,8 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.inputs_widget = inputs_widget self.inputs_layout = inputs_layout + self.add_row(is_empty=True) + def count(self): return len(self.input_fields) @@ -1311,6 +1313,8 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} + self.add_row(is_empty=True) + def count(self): return len(self.input_fields) From 18812a64e093619422e33834696a786862f9cb25 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 12:49:25 +0200 Subject: [PATCH 698/947] NOT_SET is False when used in condition --- pype/tools/config_setting/config_setting/widgets/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index 4669004b53..0d70885de7 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -11,7 +11,7 @@ class TypeToKlass: types = {} -NOT_SET = type("NOT_SET", (), {}) +NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})() METADATA_KEY = type("METADATA_KEY", (), {}) OVERRIDE_VERSION = 1 From 1d0f00dced4b4001ef0d53d84fba66e4c98e1c25 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 13:01:02 +0200 Subject: [PATCH 699/947] modifiable dict and its item has right hiearrchical update methods --- .../config_setting/config_setting/widgets/inputs.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ae0ece42bd..eeb6cf64cd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1228,6 +1228,10 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def is_modified(self): return self.is_value_modified() or self.is_key_modified() + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + self.update_style() + def update_style(self): if self.is_key_modified(): state = "modified" @@ -1374,6 +1378,11 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.update_style() + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + def update_style(self): state = self.style_state( self.is_invalid, self.is_overriden, self.is_modified From e215f2e1407ed14919892d9fb72532b2324b09f4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 13:02:51 +0200 Subject: [PATCH 700/947] inputs' set_value method does not accept global_value as kwarg --- .../config_setting/widgets/inputs.py | 59 ++++--------------- 1 file changed, 12 insertions(+), 47 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index eeb6cf64cd..57785a1bce 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -301,16 +301,11 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): # Ignore value change because if `self.isChecked()` has same # value as `value` the `_on_value_change` is not triggered self.checkbox.setChecked(value) - if global_value: - self.global_value = self.item_value() - - self._on_value_change() - def clear_value(self): self.set_value(False) @@ -417,12 +412,8 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): self.input_field.setValue(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() def clear_value(self): self.set_value(0) @@ -525,15 +516,11 @@ class TextWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): if self.multiline: self.text_input.setPlainText(value) else: self.text_input.setText(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() def reset_value(self): self.set_value(self.start_value) @@ -632,12 +619,8 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): self.path_input.setText(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() def reset_value(self): self.set_value(self.start_value) @@ -709,7 +692,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): return hint - def set_value(self, value, *, global_value=False): + def set_value(self, value): if value is NOT_SET: value = "" elif not isinstance(value, str): @@ -802,12 +785,8 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): self.text_input.set_value(value) - if global_value: - self.start_value = self.item_value() - self.global_value = self.item_value() - self._on_value_change() def reset_value(self): self.set_value(self.start_value) @@ -1020,7 +999,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): previous_inputs = tuple(self.input_fields) for item_value in value: self.add_row(value=item_value) @@ -1028,11 +1007,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): for input_field in previous_inputs: self.remove_row(input_field) - if global_value: - self.global_value = value - self.start_value = self.item_value() - self._on_value_change() - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1349,7 +1323,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self._is_modified = self.global_value != self.start_value - def set_value(self, value, *, global_value=False): + def set_value(self, value): previous_inputs = tuple(self.input_fields) for item_key, item_value in value.items(): self.add_row(key=item_key, value=item_value) @@ -1357,11 +1331,6 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): for input_field in previous_inputs: self.remove_row(input_field) - if global_value: - self.global_value = value - self.start_value = self.item_value() - self._on_value_change() - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1444,7 +1413,8 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if value is not None and key is not None: item_widget.default_key = key item_widget.key_input.setText(key) - item_widget.value_input.set_value(value, global_value=True) + item_widget.value_input.update_global_values(value) + self.hierarchical_style_update() else: self._on_value_change() self.parent().updateGeometry() @@ -2059,20 +2029,15 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): ) self._was_overriden = bool(self._is_overriden) - def set_value(self, value, *, global_value=False): + def set_value(self, value): if not self.multiplatform: - self.input_fields[0].set_value(value, global_value=global_value) + self.input_fields[0].set_value(value) else: for input_field in self.input_fields: _value = value[input_field.key] input_field.set_value(_value) - if global_value: - self.global_value = value - self.start_value = self.item_value() - self._on_value_change() - def reset_value(self): for input_field in self.input_fields: input_field.reset_value() From 87085481551cefa94c9ddf7775ab6238deeb34b4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 13:03:48 +0200 Subject: [PATCH 701/947] update_global_value can accept values for inputs used as widgets --- .../config_setting/widgets/inputs.py | 102 ++++++++++-------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 57785a1bce..ea6cd121bd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -286,15 +286,16 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): def update_global_values(self, parent_values): value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if value is not NOT_SET: - self.checkbox.setChecked(value) + if value is not NOT_SET: + self.set_value(value) - elif self.default_value is not NOT_SET: - self.checkbox.setChecked(self.default_value) + elif self.default_value is not NOT_SET: + self.set_value(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -397,15 +398,17 @@ class NumberWidget(QtWidgets.QWidget, InputObject): def update_global_values(self, parent_values): value = NOT_SET - if not self._as_widget: + if self._as_widget: + value = parent_values + else: if parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) - if value is not NOT_SET: - self.input_field.setValue(value) + if value is not NOT_SET: + self.set_value(value) - elif self.default_value is not NOT_SET: - self.input_field.setValue(self.default_value) + elif self.default_value is not NOT_SET: + self.set_value(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -501,15 +504,16 @@ class TextWidget(QtWidgets.QWidget, InputObject): def update_global_values(self, parent_values): value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if value is not NOT_SET: - self.set_value(value) + if value is not NOT_SET: + self.set_value(value) - elif self.default_value is not NOT_SET: - self.set_value(self.default_value) + elif self.default_value is not NOT_SET: + self.set_value(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -604,15 +608,16 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): def update_global_values(self, parent_values): value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if value is not NOT_SET: - self.path_input.setText(value) + if value is not NOT_SET: + self.set_value(value) - elif self.default_value is not NOT_SET: - self.path_input.setText(self.default_value) + elif self.default_value is not NOT_SET: + self.set_value(self.default_value) self.global_value = value self.start_value = self.item_value() @@ -768,21 +773,21 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def update_global_values(self, parent_values): value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if value is not NOT_SET: - self.text_input.set_value(value) + if value is not NOT_SET: + self.set_value(value) - elif self.default_value is not NOT_SET: - self.text_input.set_value(self.default_value) - - self._is_invalid = self.text_input.has_invalid_value() + elif self.default_value is not NOT_SET: + self.set_value(self.default_value) self.global_value = value self.start_value = self.item_value() + self._is_invalid = self.text_input.has_invalid_value() self._is_modified = self.global_value != self.start_value def set_value(self, value): @@ -924,6 +929,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self._parent = parent self._state = None + self._as_widget = as_widget self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) @@ -976,7 +982,9 @@ class ListWidget(QtWidgets.QWidget, InputObject): old_inputs = tuple(self.input_fields) value = NOT_SET - if parent_values is not NOT_SET: + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) self.global_value = value @@ -1300,7 +1308,9 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): old_inputs = tuple(self.input_fields) value = NOT_SET - if parent_values is not NOT_SET: + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) self.global_value = value @@ -1989,20 +1999,20 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): def update_global_values(self, parent_values): value = NOT_SET - if not self._as_widget: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) - if not self.multiplatform: - self.input_fields[0].update_global_values(parent_values) + if not self.multiplatform: + self.input_fields[0].update_global_values(parent_values) - elif self.multiplatform: - for input_field in self.input_fields: - input_field.update_global_values(value) + elif self.multiplatform: + for input_field in self.input_fields: + input_field.update_global_values(value) self.global_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value def apply_overrides(self, parent_values): From df2ecaa6696fb55b286b158783cf18435dad9cf8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 13:05:03 +0200 Subject: [PATCH 702/947] styles are updated in right way for as_widget inputs --- .../config_setting/config_setting/widgets/inputs.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ea6cd121bd..d2f7f6cae6 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1053,7 +1053,8 @@ class ListWidget(QtWidgets.QWidget, InputObject): # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if value is not None: - item_widget.value_input.set_value(value, global_value=True) + item_widget.value_input.update_global_values(value) + self.hierarchical_style_update() else: self._on_value_change() self.updateGeometry() @@ -1247,6 +1248,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self._parent = parent self._state = None + self._as_widget = as_widget self.override_value = NOT_SET self.global_value = NOT_SET @@ -1615,9 +1617,9 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.update_style() def hierarchical_style_update(self): - self.update_style() for input_field in self.input_fields: input_field.hierarchical_style_update() + self.update_style() def update_style(self, is_overriden=None): child_modified = self.child_modified @@ -1797,9 +1799,9 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): self.value_changed.emit(self) def hierarchical_style_update(self): - self.update_style() for input_field in self.input_fields: input_field.hierarchical_style_update() + self.update_style() def remove_overrides(self): self._is_overriden = False @@ -1886,6 +1888,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): "darwin": "MacOS", "linux": "Linux" } + # TODO be able to save and load with separators platform_separators = { "windows": ";", "darwin": ":", From 575ea828759f22b23ca59213b95b4dc75ae7ba45 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 13:56:32 +0200 Subject: [PATCH 703/947] discard changes and remove overrides works for roots widget --- .../config_setting/widgets/anatomy_inputs.py | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index e4f7a6383a..efbcd437ad 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -41,14 +41,13 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): super(AnatomyWidget, self).__init__(parent) self.setObjectName("AnatomyWidget") self._parent = parent + self.key = "anatomy" self._child_state = None self._state = None self._is_group = True - self.key = "anatomy" - self.override_value = NOT_SET self.start_value = NOT_SET self.global_value = NOT_SET @@ -88,7 +87,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.update_global_values(value) def set_value(self, value, *, global_value=False): - print("* set_value") + raise TypeError("AnatomyWidget does not allow to use `set_value`") def clear_value(self): print("* clear_value") @@ -97,7 +96,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): print("* _on_value_change") def update_style(self, is_overriden=None): - print("* update_style") child_modified = self.child_modified child_invalid = self.child_invalid child_state = self.style_state( @@ -154,6 +152,21 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): or self.templates_widget.child_invalid ) + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + + self.root_widget.remove_overrides() + self.templates_widget.remove_overrides() + + def discard_changes(self): + self.root_widget.discard_changes() + self.templates_widget.discard_changes() + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + def item_value(self): output = {} output.update(self.root_widget.config_value()) @@ -175,6 +188,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.root_keys = None + self.was_multiroot = NOT_SET + checkbox_widget = QtWidgets.QWidget(self) multiroot_label = QtWidgets.QLabel( "Use multiple roots", checkbox_widget @@ -220,6 +235,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): return self.multiroot_checkbox.isChecked() def update_global_values(self, parent_values): + self._is_modified = False if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) else: @@ -232,12 +248,15 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): is_multiroot = True break + self.was_multiroot = is_multiroot self.set_multiroot(is_multiroot) if is_multiroot: + self.singleroot_widget.update_global_values(NOT_SET) self.multiroot_widget.update_global_values(parent_values) else: self.singleroot_widget.update_global_values(parent_values) + self.multiroot_widget.update_global_values(NOT_SET) def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() @@ -293,6 +312,21 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: return self.singleroot_widget.child_invalid + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + self._was_overriden = False + + self.singleroot_widget.remove_overrides() + self.multiroot_widget.remove_overrides() + + def discard_changes(self): + self.singleroot_widget.discard_changes() + self.multiroot_widget.discard_changes() + + self._is_modified = self.child_modified + self._is_overriden = self._was_overriden + def item_value(self): if self.is_multiroot: return self.multiroot_widget.item_value() @@ -333,5 +367,11 @@ class TemplatesWidget(QtWidgets.QWidget): def child_invalid(self): return False + def remove_overrides(self): + pass + + def discard_changes(self): + pass + TypeToKlass.types["anatomy"] = AnatomyWidget From d6c3d95478f49bb815fce1a3c91df9ee96ee6065 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 14:40:18 +0200 Subject: [PATCH 704/947] added few missing methods to roots widget --- .../config_setting/widgets/anatomy_inputs.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index efbcd437ad..4fb01a468e 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -258,6 +258,33 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.singleroot_widget.update_global_values(parent_values) self.multiroot_widget.update_global_values(NOT_SET) + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._is_modified = False + self._state = None + self._child_state = None + + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, value) + + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + + self._is_overriden = value is not NOT_SET + self._was_overriden = bool(self._is_overriden) + + if is_multiroot: + self.singleroot_widget.apply_overrides(NOT_SET) + self.multiroot_widget.apply_overrides(value) + else: + self.singleroot_widget.apply_overrides(value) + self.multiroot_widget.apply_overrides(NOT_SET) + def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() self.multiroot_widget.hierarchical_style_update() @@ -265,6 +292,17 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def _on_multiroot_checkbox(self): self.set_multiroot(self.is_multiroot) + def _on_value_change(self, item=None): + if self.is_group and self.is_overidable: + self._is_overriden = True + + self._is_modified = ( + self.was_multiroot != self.is_multiroot + or self.child_modified + ) + + self.value_changed.emit(self) + def set_multiroot(self, is_multiroot=None): if is_multiroot is None: is_multiroot = not self.is_multiroot @@ -337,6 +375,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): return {self.key: self.item_value()} +# TODO implement class TemplatesWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(TemplatesWidget, self).__init__(parent) @@ -344,6 +383,9 @@ class TemplatesWidget(QtWidgets.QWidget): def update_global_values(self, values): pass + def apply_overrides(self, parent_values): + pass + def hierarchical_style_update(self): pass From 2a34a77d168e49f815adebe80937ca1d40078cdb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 14:40:56 +0200 Subject: [PATCH 705/947] more value changes reaction implemented --- .../config_setting/widgets/anatomy_inputs.py | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 4fb01a468e..6b174ca575 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -77,6 +77,9 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget + self.root_widget.multiroot_changed.connect(self._on_multiroot_change) + self.root_widget.value_changed.connect(self._on_value_change) + def update_global_values(self, parent_values): if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) @@ -86,14 +89,41 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.update_global_values(value) self.templates_widget.update_global_values(value) + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, value) + + self._is_overriden = value is not NOT_SET + + self.root_widget.apply_overrides(value) + self.templates_widget.apply_overrides(value) + + self._was_overriden = bool(self._is_overriden) + def set_value(self, value, *, global_value=False): raise TypeError("AnatomyWidget does not allow to use `set_value`") def clear_value(self): - print("* clear_value") + raise TypeError("AnatomyWidget does not allow to use `clear_value`") + + def _on_multiroot_change(self): + self.update_style() def _on_value_change(self, item=None): - print("* _on_value_change") + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + + self.hierarchical_style_update() + + self.value_changed.emit(self) def update_style(self, is_overriden=None): child_modified = self.child_modified @@ -154,7 +184,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False - self._is_modified = False self._was_overriden = False self.root_widget.remove_overrides() @@ -164,9 +193,13 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.discard_changes() self.templates_widget.discard_changes() - self._is_modified = self.child_modified self._is_overriden = self._was_overriden + def overrides(self): + if self.is_overriden: + return self.config_value(), True + return {self.key: {}}, True + def item_value(self): output = {} output.update(self.root_widget.config_value()) @@ -178,6 +211,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): class RootsWidget(QtWidgets.QWidget, ConfigObject): multiroot_changed = QtCore.Signal() + value_changed = QtCore.Signal(object) def __init__(self, parent): super(RootsWidget, self).__init__(parent) @@ -227,6 +261,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.multiroot_widget = multiroot_widget multiroot_checkbox.stateChanged.connect(self._on_multiroot_checkbox) + singleroot_widget.value_changed.connect(self._on_value_change) + multiroot_widget.value_changed.connect(self._on_value_change) self._on_multiroot_checkbox() From d570f287100fb325e8f10ae9c59be93cfab077d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 15:05:35 +0200 Subject: [PATCH 706/947] _was_overriden is not changed on remove overrides to keep information about change --- .../config_setting/widgets/anatomy_inputs.py | 12 ++++-------- .../config_setting/config_setting/widgets/inputs.py | 5 ----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 6b174ca575..331921ea68 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -98,14 +98,10 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): if parent_values is not NOT_SET: value = parent_values.get(self.key, value) - self._is_overriden = value is not NOT_SET - self.root_widget.apply_overrides(value) self.templates_widget.apply_overrides(value) - self._was_overriden = bool(self._is_overriden) - - def set_value(self, value, *, global_value=False): + def set_value(self, value): raise TypeError("AnatomyWidget does not allow to use `set_value`") def clear_value(self): @@ -184,7 +180,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False - self._was_overriden = False self.root_widget.remove_overrides() self.templates_widget.remove_overrides() @@ -271,7 +266,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): return self.multiroot_checkbox.isChecked() def update_global_values(self, parent_values): - self._is_modified = False + self._state = None + self._child_state = None + if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) else: @@ -296,7 +293,6 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def apply_overrides(self, parent_values): # Make sure this is set to False - self._is_modified = False self._state = None self._child_state = None diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d2f7f6cae6..81dad86c25 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -177,7 +177,6 @@ class InputObject(ConfigObject): self.set_value(self.start_value) self._is_overriden = False self._is_modified = False - self._was_overriden = False def apply_overrides(self, parent_values): self._is_modified = False @@ -1546,7 +1545,6 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - self._was_overriden = False for item in self.input_fields: item.remove_overrides() @@ -1806,7 +1804,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - self._was_overriden = False for item in self.input_fields: item.remove_overrides() @@ -2102,7 +2099,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - self._was_overriden = False for item in self.input_fields: item.remove_overrides() @@ -2234,7 +2230,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - self._was_overriden = False for item in self.input_fields: item.remove_overrides() From 38325b8d7f106296273a4b1ac28212cd33d1ec16 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 15:06:24 +0200 Subject: [PATCH 707/947] AnatomyWidget is not group --- .../config_setting/widgets/anatomy_inputs.py | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 331921ea68..96ad0c786b 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -46,13 +46,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self._child_state = None self._state = None - self._is_group = True - - self.override_value = NOT_SET - self.start_value = NOT_SET - self.global_value = NOT_SET - - self.root_keys = None self.root_widget = RootsWidget(self) self.templates_widget = TemplatesWidget(self) @@ -80,7 +73,13 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.multiroot_changed.connect(self._on_multiroot_change) self.root_widget.value_changed.connect(self._on_value_change) + def any_parent_is_group(self): + return False + def update_global_values(self, parent_values): + self._state = None + self._child_state = None + if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) else: @@ -135,17 +134,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.style().polish(self) self._child_state = child_state - state = self.style_state( - child_invalid, self.is_overriden, self.is_modified - ) - if self._state == state: - return - - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) - - self._state = state - def hierarchical_style_update(self): self.root_widget.hierarchical_style_update() self.templates_widget.hierarchical_style_update() @@ -310,6 +298,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._is_overriden = value is not NOT_SET self._was_overriden = bool(self._is_overriden) + self.was_multiroot = is_multiroot + self.set_multiroot(is_multiroot) + if is_multiroot: self.singleroot_widget.apply_overrides(NOT_SET) self.multiroot_widget.apply_overrides(value) From 7c48e2544243e832cde214f837fcc67934706af4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 16:08:18 +0200 Subject: [PATCH 708/947] apply override pass right values --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 96ad0c786b..229dff0bd9 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -303,9 +303,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot: self.singleroot_widget.apply_overrides(NOT_SET) - self.multiroot_widget.apply_overrides(value) + self.multiroot_widget.apply_overrides(parent_values) else: - self.singleroot_widget.apply_overrides(value) + self.singleroot_widget.apply_overrides(parent_values) self.multiroot_widget.apply_overrides(NOT_SET) def hierarchical_style_update(self): From bd6d5999c82ec2f7059707825ceff8787dc0e30e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 16:09:11 +0200 Subject: [PATCH 709/947] is_modified is set right on overrides --- .../config_setting/widgets/inputs.py | 82 +++++++++++++------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 81dad86c25..93ce5603aa 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -313,17 +313,15 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): if self.ignore_value_changes: return - _value = self.item_value() - is_modified = None if self.is_overidable: self._is_overriden = True - if self.override_value is not None: - is_modified = _value != self.override_value - if is_modified is None: - is_modified = _value != self.global_value - - self._is_modified = is_modified + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.global_value self.update_style() @@ -427,10 +425,16 @@ class NumberWidget(QtWidgets.QWidget, InputObject): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.global_value + self.update_style() self.value_changed.emit(self) @@ -535,10 +539,16 @@ class TextWidget(QtWidgets.QWidget, InputObject): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.global_value + self.update_style() self.value_changed.emit(self) @@ -640,10 +650,16 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.global_value if self.is_overidable: self._is_overriden = True + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.global_value + self.update_style() self.value_changed.emit(self) @@ -803,7 +819,9 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): if self.ignore_value_changes: return - self._is_invalid = self.text_input.has_invalid_value() + if self.is_overidable: + self._is_overriden = True + if self._is_invalid: self._is_modified = True elif self._is_overriden: @@ -811,9 +829,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): else: self._is_modified = self.item_value() != self.global_value - if self.is_overidable: - self._is_overriden = True - self.update_style() self.value_changed.emit(self) @@ -1017,10 +1032,17 @@ class ListWidget(QtWidgets.QWidget, InputObject): def _on_value_change(self, item=None): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.global_value + if self.is_overidable: self._is_overriden = True + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.global_value + self.update_style() self.value_changed.emit(self) @@ -1349,15 +1371,17 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if self.is_overidable: self._is_overriden = True - if self.is_overriden: + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: self._is_modified = self.item_value() != self.global_value - self.value_changed.emit(self) - self.update_style() + self.value_changed.emit(self) + def hierarchical_style_update(self): for input_field in self.input_fields: input_field.hierarchical_style_update() @@ -2019,7 +2043,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): # Make sure this is set to False self._state = None self._child_state = None - self._is_modified = False override_values = NOT_SET if parent_values is not NOT_SET: override_values = parent_values.get(self.key, override_values) @@ -2037,6 +2060,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): and self.is_overidable and self.child_overriden ) + self._is_modified = False self._was_overriden = bool(self._is_overriden) def set_value(self, value): @@ -2060,16 +2084,20 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): if self.ignore_value_changes: return - self._is_modified = self.item_value() != self.global_value - if self.is_group: - if self.is_overidable: - self._is_overriden = True - self.hierarchical_style_update() + if self.is_overidable: + self._is_overriden = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.global_value + + self.hierarchical_style_update() self.value_changed.emit(self) - self.update_style() - def update_style(self, is_overriden=None): child_modified = self.child_modified child_invalid = self.child_invalid From 0f0dd751e8e088ea286ee9e708a1252ff1e57bad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 16:10:16 +0200 Subject: [PATCH 710/947] is_multiroot deduction is better --- .../config_setting/widgets/anatomy_inputs.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 229dff0bd9..fb95f17785 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -288,12 +288,15 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if parent_values is not NOT_SET: value = parent_values.get(self.key, value) - is_multiroot = False - if isinstance(value, dict): - for _value in value.values(): - if isinstance(_value, dict): - is_multiroot = True - break + if value is NOT_SET: + is_multiroot = self.global_is_multiroot + else: + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break self._is_overriden = value is not NOT_SET self._was_overriden = bool(self._is_overriden) From 0e95344bfc5f321348c8f3c30a6bd1ca59f933ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 16:11:01 +0200 Subject: [PATCH 711/947] _on_value_changed ignore changes of not current roots widget --- .../config_setting/widgets/anatomy_inputs.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index fb95f17785..aaea588ab2 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -205,6 +205,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.root_keys = None + self.global_is_multiroot = False self.was_multiroot = NOT_SET checkbox_widget = QtWidgets.QWidget(self) @@ -219,6 +220,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): path_widget_data = { "key": "roots", + "multipath": False, "multiplatform": True, "label": "Roots" } @@ -269,6 +271,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): is_multiroot = True break + self.global_is_multiroot = is_multiroot self.was_multiroot = is_multiroot self.set_multiroot(is_multiroot) @@ -283,6 +286,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): # Make sure this is set to False self._state = None self._child_state = None + self._is_modified = False value = NOT_SET if parent_values is not NOT_SET: @@ -319,6 +323,12 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(self.is_multiroot) def _on_value_change(self, item=None): + if ( + (self.is_multiroot and item != self.multiroot_widget) + or (not self.is_multiroot and item != self.singleroot_widget) + ): + return + if self.is_group and self.is_overidable: self._is_overriden = True From 2d77236f4c05fd466ee28dc1eb17063ce22cd70b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 16:17:52 +0200 Subject: [PATCH 712/947] skip was removed on removind overrides action --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index aaea588ab2..f3fc1bfd1a 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -153,9 +153,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): @property def child_overriden(self): return ( - self.root_widget.is_overriden - or self.root_widget.child_overriden - or self.templates_widget.is_overriden + self.root_widget.child_overriden or self.templates_widget.child_overriden ) @@ -389,7 +387,6 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - self._was_overriden = False self.singleroot_widget.remove_overrides() self.multiroot_widget.remove_overrides() From b9db80e1bb7051642fa35e85100199360551623e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 4 Sep 2020 16:21:50 +0200 Subject: [PATCH 713/947] discard changes and remove overrides set multiroot widgets right way --- .../config_setting/widgets/anatomy_inputs.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index f3fc1bfd1a..8e7bb4bb8a 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -388,10 +388,18 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._is_overriden = False self._is_modified = False + self.set_multiroot(self.global_is_multiroot) + self.singleroot_widget.remove_overrides() self.multiroot_widget.remove_overrides() def discard_changes(self): + is_overriden = bool(self._is_overriden) + if is_overriden: + self.set_multiroot(self.was_multiroot) + else: + self.set_multiroot(self.global_is_multiroot) + self.singleroot_widget.discard_changes() self.multiroot_widget.discard_changes() From 89e96b6cfeb04d9b1808653f07bea81aaceade00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 09:21:00 +0200 Subject: [PATCH 714/947] modifiable widget can be used as widget --- .../config_setting/widgets/inputs.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 93ce5603aa..10dd9913b8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1292,32 +1292,33 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): main_layout.setContentsMargins(5, 5, 0, 5) main_layout.setSpacing(0) - body_widget = ExpandingWidget(input_data["label"], self) - - main_layout.addWidget(body_widget) - content_widget = QtWidgets.QWidget(self) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 0, 3) - body_widget.set_content_widget(content_widget) + if as_widget: + main_layout.addWidget(content_widget) + else: + body_widget = ExpandingWidget(input_data["label"], self) + main_layout.addWidget(body_widget) + body_widget.set_content_widget(content_widget) + + self.body_widget = body_widget + self.label_widget = body_widget.label_widget + + expandable = input_data.get("expandable", True) + if not expandable: + body_widget.hide_toolbox(hide_content=False) + else: + expanded = input_data.get("expanded", False) + if expanded: + body_widget.toggle_content() - self.body_widget = body_widget self.content_widget = content_widget self.content_layout = content_layout - self.label_widget = body_widget.label_widget - self.setAttribute(QtCore.Qt.WA_StyledBackground) - expandable = input_data.get("expandable", True) - if not expandable: - body_widget.hide_toolbox(hide_content=False) - else: - expanded = input_data.get("expanded", False) - if expanded: - body_widget.toggle_content() - self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} @@ -1402,8 +1403,9 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.setProperty("state", child_state) self.style().polish(self) - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + if not self._as_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) self._state = state @@ -2044,7 +2046,9 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self._state = None self._child_state = None override_values = NOT_SET - if parent_values is not NOT_SET: + if self._as_widget: + override_values = parent_values + elif parent_values is not NOT_SET: override_values = parent_values.get(self.key, override_values) self._is_overriden = override_values is not NOT_SET From 0ca9cf309c9e39a4b82d1efb28f66f370bc47bc1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 09:21:17 +0200 Subject: [PATCH 715/947] widgets in roots are used as widgets --- .../config_setting/widgets/anatomy_inputs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 8e7bb4bb8a..c6c06a42ae 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -222,7 +222,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): "multiplatform": True, "label": "Roots" } - singleroot_widget = PathWidget(path_widget_data, self) + singleroot_widget = PathWidget(path_widget_data, self, as_widget=True) multiroot_data = { "key": "roots", "label": "Roots", @@ -232,7 +232,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): "multiplatform": True } } - multiroot_widget = ModifiableDict(multiroot_data, self) + multiroot_widget = ModifiableDict(multiroot_data, self, as_widget=True) main_layout = QtWidgets.QVBoxLayout(self) main_layout.addWidget(checkbox_widget) @@ -275,9 +275,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot: self.singleroot_widget.update_global_values(NOT_SET) - self.multiroot_widget.update_global_values(parent_values) + self.multiroot_widget.update_global_values(value) else: - self.singleroot_widget.update_global_values(parent_values) + self.singleroot_widget.update_global_values(value) self.multiroot_widget.update_global_values(NOT_SET) def apply_overrides(self, parent_values): @@ -308,9 +308,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot: self.singleroot_widget.apply_overrides(NOT_SET) - self.multiroot_widget.apply_overrides(parent_values) + self.multiroot_widget.apply_overrides(value) else: - self.singleroot_widget.apply_overrides(parent_values) + self.singleroot_widget.apply_overrides(value) self.multiroot_widget.apply_overrides(NOT_SET) def hierarchical_style_update(self): From ed6c1609dca1b0167af3f6b2e89307da94637cb1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 10:57:54 +0200 Subject: [PATCH 716/947] fixed discard changes --- .../config_setting/widgets/inputs.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 10dd9913b8..a9ca88c171 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -201,10 +201,11 @@ class InputObject(ConfigObject): self.set_value(value) def discard_changes(self): + self._is_overriden = self._was_overriden if ( self.is_overidable + and self._was_overriden and self.override_value is not NOT_SET - and self._was_overriden is True ): self.set_value(self.override_value) else: @@ -1575,11 +1576,13 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): item.remove_overrides() def discard_changes(self): + self._is_overriden = self._was_overriden + self._is_modified = False + for item in self.input_fields: item.discard_changes() self._is_modified = self.child_modified - self._is_overriden = self._was_overriden def set_as_overriden(self): if self.is_overriden: @@ -1834,11 +1837,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): item.remove_overrides() def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + for item in self.input_fields: item.discard_changes() self._is_modified = self.child_modified - self._is_overriden = self._was_overriden def set_as_overriden(self): if self.is_overriden: @@ -2135,11 +2140,13 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): item.remove_overrides() def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + for input_field in self.input_fields: input_field.discard_changes() self._is_modified = self.child_modified - self._is_overriden = self._was_overriden @property def child_modified(self): @@ -2253,11 +2260,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): item.apply_overrides(parent_values) def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + for item in self.input_fields: item.discard_changes() self._is_modified = self.child_modified - self._is_overriden = self._was_overriden def remove_overrides(self): self._is_overriden = False From 93658f8a3ea7a8f2226fa7b48c765f0db800c227 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 11:02:55 +0200 Subject: [PATCH 717/947] added representation of object to not implemented exception --- pype/tools/config_setting/config_setting/widgets/widgets.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 42ca49d600..e547b8181a 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -353,10 +353,12 @@ class AbstractConfigObject: def set_as_overriden(self): raise NotImplementedError( - "Method `set_as_overriden` not implemented!" + "{} Method `set_as_overriden` not implemented!".format(repr(self)) ) def hierarchical_style_update(self): raise NotImplementedError( - "Method `hierarchical_style_update` not implemented!" + "{} Method `hierarchical_style_update` not implemented!".format( + repr(self) + ) ) From 208075acfaf87bdb460a423edbc5984928bbe7a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:12:36 +0200 Subject: [PATCH 718/947] list and dict items skip right mouse release --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index a9ca88c171..b5e0b2c5b6 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -932,6 +932,9 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def child_overriden(self): return self.value_input.child_overriden + def mouseReleaseEvent(self, event): + return QtWidgets.QWidget.mouseReleaseEvent(self, event) + class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) @@ -1256,6 +1259,9 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): return {} return {key: value} + def mouseReleaseEvent(self, event): + return QtWidgets.QWidget.mouseReleaseEvent(self, event) + class ModifiableDict(QtWidgets.QWidget, InputObject): # Should be used only for dictionary with one datatype as value From 78c68beb3fd2babcf9f20dd5ae9f101c7377995d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:13:55 +0200 Subject: [PATCH 719/947] removed signal multiroot_changed --- .../config_setting/widgets/anatomy_inputs.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index c6c06a42ae..0b825b90e6 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -70,7 +70,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget - self.root_widget.multiroot_changed.connect(self._on_multiroot_change) self.root_widget.value_changed.connect(self._on_value_change) def any_parent_is_group(self): @@ -106,9 +105,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): def clear_value(self): raise TypeError("AnatomyWidget does not allow to use `clear_value`") - def _on_multiroot_change(self): - self.update_style() - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -191,7 +187,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): class RootsWidget(QtWidgets.QWidget, ConfigObject): - multiroot_changed = QtCore.Signal() value_changed = QtCore.Signal(object) def __init__(self, parent): @@ -347,7 +342,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.singleroot_widget.setVisible(not is_multiroot) self.multiroot_widget.setVisible(is_multiroot) - self.multiroot_changed.emit() + self._on_value_change() @property def is_modified(self): From 8ec6d0cba5d8793cf2fe2086b696db7bf6c5944c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:14:22 +0200 Subject: [PATCH 720/947] removed label from root widgets --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 0b825b90e6..e4c555138a 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -214,13 +214,11 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): path_widget_data = { "key": "roots", "multipath": False, - "multiplatform": True, - "label": "Roots" + "multiplatform": True } singleroot_widget = PathWidget(path_widget_data, self, as_widget=True) multiroot_data = { "key": "roots", - "label": "Roots", "object_type": "path-widget", "expandable": False, "input_modifiers": { From 04d8e6d2420139f3bbb43adb4cf082d16e836694 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:20:08 +0200 Subject: [PATCH 721/947] any_parent_is_group is real attribute not function --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index e4c555138a..2f1f59c31d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -46,6 +46,8 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self._child_state = None self._state = None + self.any_parent_is_group = False + self.root_widget = RootsWidget(self) self.templates_widget = TemplatesWidget(self) @@ -72,9 +74,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.value_changed.connect(self._on_value_change) - def any_parent_is_group(self): - return False - def update_global_values(self, parent_values): self._state = None self._child_state = None From d0b05421f722397c76e8060ffb0c898742e8a489 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:21:00 +0200 Subject: [PATCH 722/947] roots widget is expandable --- .../config_setting/widgets/anatomy_inputs.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 2f1f59c31d..6a7906d46e 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -226,11 +226,21 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): } multiroot_widget = ModifiableDict(multiroot_data, self, as_widget=True) - main_layout = QtWidgets.QVBoxLayout(self) - main_layout.addWidget(checkbox_widget) - main_layout.addWidget(singleroot_widget) - main_layout.addWidget(multiroot_widget) + body_widget = ExpandingWidget("Roots", self) + content_widget = QtWidgets.QWidget(body_widget) + contnet_layout = QtWidgets.QVBoxLayout(content_widget) + contnet_layout.addWidget(checkbox_widget) + contnet_layout.addWidget(singleroot_widget) + contnet_layout.addWidget(multiroot_widget) + + body_widget.set_content_widget(content_widget) + self.label_widget = body_widget.label_widget + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.addWidget(body_widget) + + self.multiroot_label = multiroot_label self.multiroot_checkbox = multiroot_checkbox self.singleroot_widget = singleroot_widget self.multiroot_widget = multiroot_widget From 4e0afbd300b48b0bfcdfd465b92f9e84fa4761e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:21:22 +0200 Subject: [PATCH 723/947] added mutliroot state for multiroot checkbox label --- .../config_setting/widgets/anatomy_inputs.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 6a7906d46e..81a7357c4d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -192,10 +192,13 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): super(RootsWidget, self).__init__(parent) self.setObjectName("RootsWidget") self._parent = parent - self._is_group = True self.key = "roots" - self.root_keys = None + self._state = None + self._multiroot_state = None + + self._is_group = True + self.any_parent_is_group = False self.global_is_multiroot = False self.was_multiroot = NOT_SET @@ -257,7 +260,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def update_global_values(self, parent_values): self._state = None - self._child_state = None + self._multiroot_state = None if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) @@ -285,7 +288,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def apply_overrides(self, parent_values): # Make sure this is set to False self._state = None - self._child_state = None + self._multiroot_state = None self._is_modified = False value = NOT_SET From 41d92b03afb3e89610562493058ab99de73c426f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:22:48 +0200 Subject: [PATCH 724/947] roots value change can ignore changes and do what should if item is not defined --- .../config_setting/widgets/anatomy_inputs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 81a7357c4d..c34418be87 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -326,7 +326,12 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(self.is_multiroot) def _on_value_change(self, item=None): - if ( + if self.ignore_value_changes: + return + + if item is None: + pass + elif ( (self.is_multiroot and item != self.multiroot_widget) or (not self.is_multiroot and item != self.singleroot_widget) ): From 2d778ae37a43074f06be89be5e2bdbfb6770b94d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:24:01 +0200 Subject: [PATCH 725/947] discard changes sets right multiroot on discard changes --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index c34418be87..5817d67274 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -404,8 +404,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.multiroot_widget.remove_overrides() def discard_changes(self): - is_overriden = bool(self._is_overriden) - if is_overriden: + self._is_overriden = self._was_overriden + self._is_modified = False + if self._is_overriden: self.set_multiroot(self.was_multiroot) else: self.set_multiroot(self.global_is_multiroot) @@ -414,7 +415,6 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.multiroot_widget.discard_changes() self._is_modified = self.child_modified - self._is_overriden = self._was_overriden def item_value(self): if self.is_multiroot: From d35c2b62ff3edcd7a2ad0660fefc6dfdc697746c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:24:13 +0200 Subject: [PATCH 726/947] roots widget has update style --- .../config_setting/widgets/anatomy_inputs.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 5817d67274..df5be5108d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -321,6 +321,37 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() self.multiroot_widget.hierarchical_style_update() + self.update_style() + + def update_style(self): + multiroot_state = self.style_state( + False, + self.is_overriden, + self.was_multiroot and not self.is_multiroot + ) + if multiroot_state != self._multiroot_state: + self.multiroot_label.setProperty("state", multiroot_state) + self.multiroot_label.style().polish(self.multiroot_label) + self._multiroot_state = multiroot_state + + state = self.style_state( + self.is_invalid, self.is_overriden, self.is_modified + ) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.setProperty("state", child_state) + self.style().polish(self) + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state def _on_multiroot_checkbox(self): self.set_multiroot(self.is_multiroot) From 9c1e18cc103959b29788f15784136d1ff7b32bd4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 12:24:30 +0200 Subject: [PATCH 727/947] multiroot widget apply parent values --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index df5be5108d..85f562eb8b 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -313,7 +313,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot: self.singleroot_widget.apply_overrides(NOT_SET) - self.multiroot_widget.apply_overrides(value) + self.multiroot_widget.apply_overrides(parent_values) else: self.singleroot_widget.apply_overrides(value) self.multiroot_widget.apply_overrides(NOT_SET) From ec80578304e9f61affee1f63b4ff7b80ebe295e8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 14:15:02 +0200 Subject: [PATCH 728/947] anatomy widget does not care about is overriden --- .../config_setting/widgets/anatomy_inputs.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 85f562eb8b..e918c97bf8 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -108,9 +108,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): if self.ignore_value_changes: return - if self.is_overidable: - self._is_overriden = True - self.hierarchical_style_update() self.value_changed.emit(self) @@ -160,8 +157,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): ) def remove_overrides(self): - self._is_overriden = False - self.root_widget.remove_overrides() self.templates_widget.remove_overrides() @@ -169,8 +164,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.discard_changes() self.templates_widget.discard_changes() - self._is_overriden = self._was_overriden - def overrides(self): if self.is_overriden: return self.config_value(), True @@ -305,9 +298,6 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): is_multiroot = True break - self._is_overriden = value is not NOT_SET - self._was_overriden = bool(self._is_overriden) - self.was_multiroot = is_multiroot self.set_multiroot(is_multiroot) From 964241286bc0d7ec4b884413268ddfcd7750a7b5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 14:16:01 +0200 Subject: [PATCH 729/947] roots widget cares about is overriden and fixed multiroot state --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index e918c97bf8..a1a0879dc2 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -302,12 +302,16 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(is_multiroot) if is_multiroot: + self._is_overriden = parent_values is not NOT_SET self.singleroot_widget.apply_overrides(NOT_SET) self.multiroot_widget.apply_overrides(parent_values) else: + self._is_overriden = value is not NOT_SET self.singleroot_widget.apply_overrides(value) self.multiroot_widget.apply_overrides(NOT_SET) + self._was_overriden = bool(self._is_overriden) + def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() self.multiroot_widget.hierarchical_style_update() @@ -317,7 +321,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): multiroot_state = self.style_state( False, self.is_overriden, - self.was_multiroot and not self.is_multiroot + self.was_multiroot != self.is_multiroot ) if multiroot_state != self._multiroot_state: self.multiroot_label.setProperty("state", multiroot_state) From 5f6687cd9687bcdba276d686ab8823cd8e800075 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:11:57 +0200 Subject: [PATCH 730/947] inputs can have different parent widget than input parent --- .../config_setting/widgets/inputs.py | 83 +++++++++++++------ 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b5e0b2c5b6..ea40b6d25e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -248,9 +248,12 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(BooleanWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(BooleanWidget, self).__init__(parent_widget) self._parent = parent self._as_widget = as_widget @@ -353,9 +356,12 @@ class NumberWidget(QtWidgets.QWidget, InputObject): input_modifiers = ("minimum", "maximum", "decimal") def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(NumberWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(NumberWidget, self).__init__(parent_widget) self._parent = parent self._as_widget = as_widget @@ -465,9 +471,12 @@ class TextWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(TextWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(TextWidget, self).__init__(parent_widget) self._parent = parent self._as_widget = as_widget @@ -582,9 +591,12 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(PathInputWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(PathInputWidget, self).__init__(parent_widget) self._parent = parent self._as_widget = as_widget @@ -742,9 +754,12 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(RawJsonWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(RawJsonWidget, self).__init__(parent_widget) self._parent = parent self._as_widget = as_widget @@ -940,9 +955,12 @@ class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(ListWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(ListWidget, self).__init__(parent_widget) self.setObjectName("ListWidget") self._parent = parent @@ -1269,9 +1287,12 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(ModifiableDict, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(ModifiableDict, self).__init__(parent_widget) self.setObjectName("ModifiableDict") self._parent = parent @@ -1483,14 +1504,17 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): if as_widget: raise TypeError("Can't use \"{}\" as widget item.".format( self.__class__.__name__ )) - super(DictWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(DictWidget, self).__init__(parent_widget) self.setObjectName("DictWidget") self._state = None @@ -1743,8 +1767,14 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): allow_actions = False def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): + if parent_widget is None: + parent_widget = parent + super(DictInvisible, self).__init__(parent_widget) + self.setObjectName("DictInvisible") + self._parent = parent any_parent_is_group = parent.is_group @@ -1754,9 +1784,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) - super(DictInvisible, self).__init__(parent) - self.setObjectName("DictInvisible") - self.setAttribute(QtCore.Qt.WA_StyledBackground) layout = QtWidgets.QVBoxLayout(self) @@ -1930,9 +1957,12 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): } def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): - super(PathWidget, self).__init__(parent) + if parent_widget is None: + parent_widget = parent + super(PathWidget, self).__init__(parent_widget) self._parent = parent self._state = None @@ -2214,8 +2244,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): allow_actions = False def __init__( - self, input_data, parent, as_widget=False, label_widget=None + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None ): + if parent_widget is None: + parent_widget = parent + super(DictFormWidget, self).__init__(parent_widget) + self._parent = parent any_parent_is_group = parent.is_group @@ -2226,8 +2261,6 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self._is_group = False - super(DictFormWidget, self).__init__(parent) - self.input_fields = [] self.content_layout = QtWidgets.QFormLayout(self) From 4b63222ac76bcbd6805168125905cb56a98cc276 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:20:15 +0200 Subject: [PATCH 731/947] fixed name of content layour in roots --- .../config_setting/widgets/anatomy_inputs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index a1a0879dc2..eec6bf2a13 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -225,10 +225,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): body_widget = ExpandingWidget("Roots", self) content_widget = QtWidgets.QWidget(body_widget) - contnet_layout = QtWidgets.QVBoxLayout(content_widget) - contnet_layout.addWidget(checkbox_widget) - contnet_layout.addWidget(singleroot_widget) - contnet_layout.addWidget(multiroot_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.addWidget(checkbox_widget) + content_layout.addWidget(singleroot_widget) + content_layout.addWidget(multiroot_widget) body_widget.set_content_widget(content_widget) self.label_widget = body_widget.label_widget From f8d925f7121a2ebc211cd89e9c666acf3af03261 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:20:32 +0200 Subject: [PATCH 732/947] templates widget has something in --- .../config_setting/widgets/anatomy_inputs.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index eec6bf2a13..a3d3e74e48 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -456,6 +456,20 @@ class TemplatesWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(TemplatesWidget, self).__init__(parent) + body_widget = ExpandingWidget("Templates", self) + content_widget = QtWidgets.QWidget(body_widget) + body_widget.set_content_widget(content_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + + label = QtWidgets.QLabel("Nothing yet", content_widget) + content_layout.addWidget(label) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + layout.addWidget(body_widget) + def update_global_values(self, values): pass From 35482ef9737d254ec62ca951ec5320a0b996765a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:21:08 +0200 Subject: [PATCH 733/947] item_value o dictionary return all keys event if are empty --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ea40b6d25e..3b40775718 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1440,9 +1440,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def item_value(self): output = {} for item in self.input_fields: - item_value = item.config_value() - if item_value: - output.update(item_value) + output.update(item.config_value()) return output def add_row(self, row=None, key=None, value=None, is_empty=False): From a4424f6f5099c624ab2bb91941877c346ed01888 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:28:37 +0200 Subject: [PATCH 734/947] dictionary item has key_vlaue instead of _key --- .../tools/config_setting/config_setting/widgets/inputs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3b40775718..ad01ea2ad9 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1206,16 +1206,16 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.value_input.value_changed.connect(self._on_value_change) # TODO This doesn't make sence! - self.default_key = self._key() self.global_value = self.value_input.item_value() + self.origin_key = self.key_value() self.override_key = NOT_SET self.override_value = NOT_SET + def key_value(self): + return self.key_input.text() self.is_single = False - def _key(self): - return self.key_input.text() def _on_value_change(self, item=None): self.update_style() @@ -1245,7 +1245,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): return self._parent.any_parent_is_group def is_key_modified(self): - return self._key() != self.default_key + return self.key_value() != self.origin_key def is_value_modified(self): return self.value_input.is_modified From 147f3f29d99f91e0786fff2549e1f7ee16b6f2be Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:29:31 +0200 Subject: [PATCH 735/947] removed unnecessary attributes --- .../config_setting/config_setting/widgets/inputs.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ad01ea2ad9..9f45a2211c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1166,6 +1166,9 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self._parent = config_parent + self.is_single = False + self.is_key_duplicated = False + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) @@ -1205,16 +1208,11 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.key_input.textChanged.connect(self._on_value_change) self.value_input.value_changed.connect(self._on_value_change) - # TODO This doesn't make sence! - self.global_value = self.value_input.item_value() self.origin_key = self.key_value() - self.override_key = NOT_SET - self.override_value = NOT_SET def key_value(self): return self.key_input.text() - self.is_single = False def _on_value_change(self, item=None): @@ -1273,8 +1271,6 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def config_value(self): key = self.key_input.text() value = self.value_input.item_value() - if not key: - return {} return {key: value} def mouseReleaseEvent(self, event): From a7d6eabe325daed58109f4b325adf737fa2b239f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:29:46 +0200 Subject: [PATCH 736/947] dict item has set_values --- .../config_setting/config_setting/widgets/inputs.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9f45a2211c..21ee1fc6e8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1219,6 +1219,11 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.update_style() self.value_changed.emit(self) + def set_values(self, key, value): + self.origin_key = key + self.key_input.setText(key) + self.value_input.update_global_values(value) + @property def is_group(self): return self._parent.is_group @@ -1470,9 +1475,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: - item_widget.default_key = key - item_widget.key_input.setText(key) - item_widget.value_input.update_global_values(value) + item_widget.set_values(key, value) self.hierarchical_style_update() else: self._on_value_change() From 51b4bc4e7dfb24cc7eaf7ae4bdd3ea2b4efa7b03 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:30:02 +0200 Subject: [PATCH 737/947] basic logic of key is valid in dict item --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 21ee1fc6e8..476fc0c69f 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1213,7 +1213,13 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def key_value(self): return self.key_input.text() + def is_key_valid(self): + if self.key_value() == "": + return False + if self.is_key_duplicated: + return False + return True def _on_value_change(self, item=None): self.update_style() From 0a4379557cb9a01195cc298fccc6b21c37bfd6d9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:36:53 +0200 Subject: [PATCH 738/947] added duplication invalidation for dictionary item --- .../config_setting/widgets/inputs.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 476fc0c69f..1f21f35d6b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1,5 +1,6 @@ import json import logging +import collections from Qt import QtWidgets, QtCore, QtGui from .widgets import ( AbstractConfigObject, @@ -1268,7 +1269,9 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.update_style() def update_style(self): - if self.is_key_modified(): + if not self.is_key_valid(): + state = "invalid" + elif self.is_key_modified(): state = "modified" else: state = "" @@ -1401,6 +1404,23 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.remove_row(input_field) def _on_value_change(self, item=None): + fields_by_keys = collections.defaultdict(list) + for input_field in self.input_fields: + key = input_field.key_value() + fields_by_keys[key].append(input_field) + + any_invalid = False + for fields in fields_by_keys.values(): + if len(fields) == 1: + field = fields[0] + if field.is_key_duplicated: + field.is_key_duplicated = False + field.update_style() + else: + for field in fields: + field.is_key_duplicated = True + field.update_style() + if self.ignore_value_changes: return From ce7e11e12c95aef286ee6029059edcd29a6f7dc2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:37:30 +0200 Subject: [PATCH 739/947] removed unused variable --- pype/tools/config_setting/config_setting/widgets/inputs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 1f21f35d6b..4a2d955ae4 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1409,7 +1409,6 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): key = input_field.key_value() fields_by_keys[key].append(input_field) - any_invalid = False for fields in fields_by_keys.values(): if len(fields) == 1: field = fields[0] From 0053379aa37aa617a9cf52bffd4b5a3bc5e244f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 18:43:30 +0200 Subject: [PATCH 740/947] modifiable dict is validated in right way --- .../config_setting/widgets/inputs.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4a2d955ae4..2d23085854 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1214,13 +1214,13 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def key_value(self): return self.key_input.text() - def is_key_valid(self): + def is_key_invalid(self): if self.key_value() == "": - return False + return True if self.is_key_duplicated: - return False - return True + return True + return False def _on_value_change(self, item=None): self.update_style() @@ -1268,8 +1268,12 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.value_input.hierarchical_style_update() self.update_style() + @property + def is_invalid(self): + return self.is_key_invalid() or self.value_input.is_invalid + def update_style(self): - if not self.is_key_valid(): + if self.is_key_invalid(): state = "invalid" elif self.is_key_modified(): state = "modified" @@ -1426,7 +1430,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if self.is_overidable: self._is_overriden = True - if self._is_invalid: + if self.is_invalid: self._is_modified = True elif self._is_overriden: self._is_modified = self.item_value() != self.override_value @@ -1520,6 +1524,17 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self._on_value_change() self.parent().updateGeometry() + @property + def is_invalid(self): + return self._is_invalid or self.child_invalid + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.is_invalid: + return True + return False + # Dictionaries class DictWidget(QtWidgets.QWidget, ConfigObject): From 4d109fb4e2aca4457f245b9edcf226c365e5a3a8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 19:08:06 +0200 Subject: [PATCH 741/947] roots widget at right place --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index a3d3e74e48..9e69d7234e 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -234,6 +234,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(body_widget) self.multiroot_label = multiroot_label From 5350e7a50592a53a2d9b6462647372629060f2cb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 7 Sep 2020 19:22:15 +0200 Subject: [PATCH 742/947] clickable widget does not override init --- pype/tools/config_setting/config_setting/widgets/widgets.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index e547b8181a..dff966fa36 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -57,10 +57,6 @@ class PathInput(QtWidgets.QLineEdit): class ClickableWidget(QtWidgets.QLabel): clicked = QtCore.Signal() - def __init__(self, *args, **kwargs): - super(ClickableWidget, self).__init__(*args, **kwargs) - self.setObjectName("ExpandLabel") - def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.LeftButton: self.clicked.emit() From 8ef37d3dfa565c22f776cea73e257f0000d07e64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:46:07 +0200 Subject: [PATCH 743/947] defined child offset for nested hierarchy --- pype/tools/config_setting/config_setting/widgets/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index 0d70885de7..08b0dfc3c4 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -14,6 +14,7 @@ class TypeToKlass: NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})() METADATA_KEY = type("METADATA_KEY", (), {}) OVERRIDE_VERSION = 1 +CHILD_OFFSET = 15 def convert_gui_data_to_overrides(data, first=True): From de9e7811e40f64093ee92435d93beb2b95b7509b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:46:22 +0200 Subject: [PATCH 744/947] clickable widget is not qlabel but qwidget --- pype/tools/config_setting/config_setting/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index dff966fa36..a2e3e058b0 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -54,7 +54,7 @@ class PathInput(QtWidgets.QLineEdit): self.clear_end_path() -class ClickableWidget(QtWidgets.QLabel): +class ClickableWidget(QtWidgets.QWidget): clicked = QtCore.Signal() def mouseReleaseEvent(self, event): From 4829bbc473963187963392749634851d8283eab6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:46:48 +0200 Subject: [PATCH 745/947] expandable widget has one more widget for showing the left side line --- .../config_setting/widgets/widgets.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index a2e3e058b0..19d9ad9d25 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -82,40 +82,41 @@ class ExpandingWidget(QtWidgets.QWidget): label_widget = QtWidgets.QLabel(label, parent=top_part) label_widget.setObjectName("DictLabel") - layout = QtWidgets.QHBoxLayout(top_part) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(5) - layout.addWidget(button_toggle) - layout.addWidget(label_widget) - top_part.setLayout(layout) + side_line_widget = QtWidgets.QWidget(top_part) + side_line_widget.setObjectName("SideLineWidget") + side_line_layout = QtWidgets.QHBoxLayout(side_line_widget) + side_line_layout.setContentsMargins(5, 10, 0, 10) + side_line_layout.addWidget(button_toggle) + side_line_layout.addWidget(label_widget) + + top_part_layout = QtWidgets.QHBoxLayout(top_part) + top_part_layout.setContentsMargins(0, 0, 0, 0) + top_part_layout.addWidget(side_line_widget) self.setAttribute(QtCore.Qt.WA_StyledBackground) self.top_part = top_part + self.side_line_widget = side_line_widget self.button_toggle = button_toggle self.label_widget = label_widget self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) + self.main_layout = QtWidgets.QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + self.main_layout.addWidget(self.top_part) + def hide_toolbox(self, hide_content=False): self.button_toggle.setArrowType(QtCore.Qt.NoArrow) self.toolbox_hidden = True self.content_widget.setVisible(not hide_content) self.parent().updateGeometry() - def set_content_widget(self, content_widget, margins=None): - main_layout = QtWidgets.QVBoxLayout(self) - if margins is None: - margins = (4, 4, 0, 4) - main_layout.setContentsMargins(*margins) - + def set_content_widget(self, content_widget): content_widget.setVisible(False) - - main_layout.addWidget(self.top_part) - main_layout.addWidget(content_widget) - self.setLayout(main_layout) - + self.main_layout.addWidget(content_widget) self.content_widget = content_widget def _top_part_clicked(self): From 58272c472bc1921127d13339c9d96de2ed220c26 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:47:37 +0200 Subject: [PATCH 746/947] modifications to match new expandable widget content properties --- .../config_setting/widgets/inputs.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 2d23085854..4347b1336d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -8,7 +8,7 @@ from .widgets import ( NumberSpinBox, PathInput ) -from .lib import NOT_SET, METADATA_KEY, TypeToKlass +from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET class ConfigObject(AbstractConfigObject): @@ -1331,12 +1331,12 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.key = input_data["key"] main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(5, 5, 0, 5) + main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) content_widget = QtWidgets.QWidget(self) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 0, 3) + content_layout.setContentsMargins(CHILD_OFFSET, 3, 0, 3) if as_widget: main_layout.addWidget(content_widget) @@ -1572,7 +1572,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.key = input_data["key"] main_layout = QtWidgets.QHBoxLayout(self) - main_layout.setContentsMargins(5, 5, 0, 5) + main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) body_widget = ExpandingWidget(input_data["label"], self) @@ -1581,7 +1581,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): content_widget = QtWidgets.QWidget(body_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(3, 3, 0, 3) + content_layout.setContentsMargins(CHILD_OFFSET, 3, 0, 3) body_widget.set_content_widget(content_widget) @@ -1725,8 +1725,10 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): child_state = "child-{}".format(child_state) if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) self._child_state = child_state state = self.style_state( @@ -2300,6 +2302,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): self.input_fields = [] self.content_layout = QtWidgets.QFormLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) for child_data in input_data.get("children", []): self.add_children_gui(child_data) From 3a566b306f1857d86a02dff264862434bc9491eb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:47:51 +0200 Subject: [PATCH 747/947] only SideLineWidget has changing border colors --- .../config_setting/style/style.css | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 638bf1c6fb..9051130344 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -123,7 +123,7 @@ QPushButton[btn-type="expand-toggle"] { #DictLabel { font-weight: bold; } -#ModifiableDict, #DictWidget, #AnatomyWidget { +#SideLineWidget { border-style: solid; border-color: #455c6e; border-left-width: 3px; @@ -131,36 +131,36 @@ QPushButton[btn-type="expand-toggle"] { border-right-width: 0px; border-top-width: 0px; } -#ModifiableDict:hover, #DictWidget:hover, #AnatomyWidget:hover { +#SideLineWidget:hover { border-color: #62839d; } -#ModifiableDict[state="child-modified"], #DictWidget[state="child-modified"], #AnatomyWidget[state="child-modified"] { +#SideLineWidget[state="child-modified"]{ border-color: #106aa2; } -#ModifiableDict[state="child-modified"]:hover, #DictWidget[state="child-modified"]:hover, #AnatomyWidget[state="child-modified"]:hover { +#SideLineWidget[state="child-modified"]:hover{ border-color: #137cbd; } -#ModifiableDict[state="child-invalid"], #DictWidget[state="child-invalid"], #AnatomyWidget[state="child-invalid"] { +#SideLineWidget[state="child-invalid"]{ border-color: #ad2e2e; } -#ModifiableDict[state="child-invalid"]:hover, #DictWidget[state="child-invalid"]:hover, #AnatomyWidget[state="child-invalid"]:hover { +#SideLineWidget[state="child-invalid"]:hover{ border-color: #c93636; } -#ModifiableDict[state="child-overriden"], #DictWidget[state="child-overriden"], #AnatomyWidget[state="child-overriden"] { +#SideLineWidget[state="child-overriden"]{ border-color: #e67300; } -#ModifiableDict[state="child-overriden"]:hover, #DictWidget[state="child-overriden"]:hover, #AnatomyWidget[state="child-overriden"]:hover { +#SideLineWidget[state="child-overriden"]:hover { border-color: #ff8c1a; } -#ModifiableDict[state="child-overriden-modified"], #DictWidget[state="child-overriden-modified"], #AnatomyWidget[state="child-modified"] { +#SideLineWidget[state="child-overriden-modified"] { border-color: #106aa2; } -#ModifiableDict[state="child-overriden-modified"]:hover, #DictWidget[state="child-overriden-modified"]:hover, #AnatomyWidget[state="child-modified"]:hover { +#SideLineWidget[state="child-overriden-modified"]:hover { border-color: #137cbd; } From bafc82364df22bc9ab8e684232d1adfacb6b5849 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:54:44 +0200 Subject: [PATCH 748/947] top part of expandable widget is not used elsewhere --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 +- pype/tools/config_setting/config_setting/widgets/widgets.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4347b1336d..a66f121b3d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1631,7 +1631,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): ) item.value_changed.connect(self._on_value_change) - self.body_widget.top_part.layout().addWidget(item) + self.body_widget.side_line_layout.addWidget(item) self.checkbox_widget = item self.input_fields.append(item) return item diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 19d9ad9d25..8291f56e13 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -95,18 +95,18 @@ class ExpandingWidget(QtWidgets.QWidget): self.setAttribute(QtCore.Qt.WA_StyledBackground) - self.top_part = top_part self.side_line_widget = side_line_widget + self.side_line_layout = side_line_layout self.button_toggle = button_toggle self.label_widget = label_widget - self.top_part.clicked.connect(self._top_part_clicked) + top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) self.main_layout = QtWidgets.QVBoxLayout(self) self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setSpacing(0) - self.main_layout.addWidget(self.top_part) + self.main_layout.addWidget(top_part) def hide_toolbox(self, hide_content=False): self.button_toggle.setArrowType(QtCore.Qt.NoArrow) From e453b1c816084b957244cc5576e932d48aee6e6e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 10:54:57 +0200 Subject: [PATCH 749/947] add bg color to dict parts --- pype/tools/config_setting/config_setting/style/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 9051130344..3c8cc59182 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -123,7 +123,9 @@ QPushButton[btn-type="expand-toggle"] { #DictLabel { font-weight: bold; } + #SideLineWidget { + background-color: #31424e; border-style: solid; border-color: #455c6e; border-left-width: 3px; @@ -131,11 +133,11 @@ QPushButton[btn-type="expand-toggle"] { border-right-width: 0px; border-top-width: 0px; } + #SideLineWidget:hover { border-color: #62839d; } - #SideLineWidget[state="child-modified"]{ border-color: #106aa2; } From 19dd51fb98d53f19e010d13681fc140981604241 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 11:48:56 +0200 Subject: [PATCH 750/947] set checkbox spacing --- pype/tools/config_setting/config_setting/style/style.css | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 3c8cc59182..079d2f697e 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -19,12 +19,11 @@ QMenu::item:selected { border-left-color: #61839e; background-color: #222d37; } - -QCheckBox::indicator { -} -QCheckBox::indicator:focus { - color: #ff0000; +QCheckBox { + spacing: 0px; } +QCheckBox::indicator {} +QCheckBox::indicator:focus {} QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit { border: 1px solid #aaaaaa; From 842aeaf6de0d2d75d80b3af06a08190ea706bb28 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 11:53:22 +0200 Subject: [PATCH 751/947] expanding widget has more widgets --- .../config_setting/widgets/widgets.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 8291f56e13..31c7df4704 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -95,6 +95,10 @@ class ExpandingWidget(QtWidgets.QWidget): self.setAttribute(QtCore.Qt.WA_StyledBackground) + self.top_part_ending = None + self.after_label_layout = None + self.end_of_layout = None + self.side_line_widget = side_line_widget self.side_line_layout = side_line_layout self.button_toggle = button_toggle @@ -137,6 +141,47 @@ class ExpandingWidget(QtWidgets.QWidget): self.content_widget.setVisible(checked) self.parent().updateGeometry() + def add_widget_after_label(self, widget): + self._add_side_widget_subwidgets() + self.after_label_layout.addWidget(widget) + + def _add_side_widget_subwidgets(self): + if self.top_part_ending is not None: + return + + top_part_ending = QtWidgets.QWidget(self.side_line_widget) + top_part_ending.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + top_part_ending_layout = QtWidgets.QHBoxLayout(top_part_ending) + top_part_ending_layout.setContentsMargins(0, 0, 0, 0) + top_part_ending_layout.setSpacing(0) + top_part_ending_layout.setAlignment(QtCore.Qt.AlignVCenter) + + after_label_widget = QtWidgets.QWidget(top_part_ending) + spacer_item = QtWidgets.QWidget(top_part_ending) + end_of_widget = QtWidgets.QWidget(top_part_ending) + + self.after_label_layout = QtWidgets.QVBoxLayout(after_label_widget) + self.after_label_layout.setContentsMargins(0, 0, 0, 0) + + self.end_of_layout = QtWidgets.QVBoxLayout(end_of_widget) + self.end_of_layout.setContentsMargins(0, 0, 0, 0) + + spacer_layout = QtWidgets.QVBoxLayout(spacer_item) + spacer_layout.setContentsMargins(0, 0, 0, 0) + + top_part_ending_layout.addWidget(after_label_widget, 0) + top_part_ending_layout.addWidget(spacer_item, 1) + top_part_ending_layout.addWidget(end_of_widget, 0) + + top_part_ending.setAttribute(QtCore.Qt.WA_TranslucentBackground) + after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_item.setAttribute(QtCore.Qt.WA_TranslucentBackground) + end_of_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.top_part_ending = top_part_ending + self.side_line_layout.addWidget(top_part_ending) + def resizeEvent(self, event): super(ExpandingWidget, self).resizeEvent(event) self.content_widget.updateGeometry() From 47da6e688449b5c68eeaa179da618ba38cc2526c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 11:54:13 +0200 Subject: [PATCH 752/947] dict widget use different way of adding checkbox to top --- pype/tools/config_setting/config_setting/widgets/inputs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index a66f121b3d..f188471df8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -282,7 +282,6 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.label_widget = label_widget self.checkbox = QtWidgets.QCheckBox(self) - self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) layout.addWidget(self.checkbox, 1) self.setFocusProxy(self.checkbox) @@ -1631,7 +1630,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): ) item.value_changed.connect(self._on_value_change) - self.body_widget.side_line_layout.addWidget(item) + self.body_widget.add_widget_after_label(item) self.checkbox_widget = item self.input_fields.append(item) return item From 37f901ad0979ad0875fc65f68e2bc406d74ec8d9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 11:57:17 +0200 Subject: [PATCH 753/947] dict widget has right margins --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f188471df8..bed0e60339 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1580,7 +1580,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): content_widget = QtWidgets.QWidget(body_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(CHILD_OFFSET, 3, 0, 3) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) body_widget.set_content_widget(content_widget) From 03470eb75d9e46f62a35a443218ef3281fc7632b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 12:28:51 +0200 Subject: [PATCH 754/947] changed styled background to transparent in most of cases --- .../config_setting/config_setting/widgets/inputs.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index bed0e60339..f175cb159a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -277,7 +277,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout.addWidget(label_widget, 0) self.label_widget = label_widget @@ -993,7 +993,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.label_widget = label_widget inputs_widget = QtWidgets.QWidget(self) - inputs_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + inputs_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout.addWidget(inputs_widget) inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) @@ -1358,7 +1358,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.content_widget = content_widget self.content_layout = content_layout - self.setAttribute(QtCore.Qt.WA_StyledBackground) + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.object_type = input_data["object_type"] self.default_value = input_data.get("default", NOT_SET) @@ -1590,7 +1590,6 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget - self.setAttribute(QtCore.Qt.WA_StyledBackground) self.checkbox_widget = None self.checkbox_key = input_data.get("checkbox_key") @@ -1822,7 +1821,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): self.any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) - self.setAttribute(QtCore.Qt.WA_StyledBackground) + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -2041,7 +2040,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) layout.addWidget(label_widget, 0) self.label_widget = label_widget From 7794f5a20dcbd593d5b28551ef39e264f976ea4c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 12:29:08 +0200 Subject: [PATCH 755/947] chceckbox widget has spacer at the end --- pype/tools/config_setting/config_setting/widgets/inputs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f175cb159a..6fb1f553bd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -282,7 +282,12 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.label_widget = label_widget self.checkbox = QtWidgets.QCheckBox(self) - layout.addWidget(self.checkbox, 1) + spacer = QtWidgets.QWidget(self) + layout.addWidget(self.checkbox, 0) + layout.addWidget(spacer, 1) + + spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) + self.setFocusProxy(self.checkbox) self.checkbox.stateChanged.connect(self._on_value_change) From f21c9426aaa4bdf6cc14f687694b5e402f1773bf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 12:29:25 +0200 Subject: [PATCH 756/947] expanding widget is transparent too --- pype/tools/config_setting/config_setting/widgets/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 31c7df4704..a76f4f6f35 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -93,7 +93,7 @@ class ExpandingWidget(QtWidgets.QWidget): top_part_layout.setContentsMargins(0, 0, 0, 0) top_part_layout.addWidget(side_line_widget) - self.setAttribute(QtCore.Qt.WA_StyledBackground) + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.top_part_ending = None self.after_label_layout = None From 5224572d23284f5a7bc26c45e19a6ed852318673 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 12:30:23 +0200 Subject: [PATCH 757/947] content widget has darker and darker background --- pype/tools/config_setting/config_setting/style/style.css | 4 ++++ pype/tools/config_setting/config_setting/widgets/inputs.py | 1 + 2 files changed, 5 insertions(+) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 079d2f697e..f238246063 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -123,6 +123,10 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } +#ContentWidget { + background-color: rgba(19, 26, 32, 20%); +} + #SideLineWidget { background-color: #31424e; border-style: solid; diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 6fb1f553bd..221d02ca65 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1584,6 +1584,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): main_layout.addWidget(body_widget) content_widget = QtWidgets.QWidget(body_widget) + content_widget.setObjectName("ContentWidget") content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) From 53bbafcb244d9aa9647723486bbe365bfeaa2750 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 13:55:51 +0200 Subject: [PATCH 758/947] ContentWidget is transparent by default --- pype/tools/config_setting/config_setting/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index f238246063..af61655a36 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -124,6 +124,9 @@ QPushButton[btn-type="expand-toggle"] { } #ContentWidget { + background-color: transparent; +} +#ContentWidget[content_state="hightlighted"] { background-color: rgba(19, 26, 32, 20%); } From 1dabf20d363d7f6331c933a9525e6571d61ebd89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 13:56:15 +0200 Subject: [PATCH 759/947] dict widget can have set if should highlight background --- .../config_setting/config_setting/widgets/inputs.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 221d02ca65..bd046e32dd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1571,6 +1571,11 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) + if input_data.get("highlight_content", False): + content_state = "hightlighted" + else: + content_state = "" + self.input_fields = [] self.key = input_data["key"] @@ -1585,6 +1590,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): content_widget = QtWidgets.QWidget(body_widget) content_widget.setObjectName("ContentWidget") + content_widget.setProperty("content_state", content_state) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) @@ -1596,7 +1602,6 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget - self.checkbox_widget = None self.checkbox_key = input_data.get("checkbox_key") @@ -2311,6 +2316,8 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): for child_data in input_data.get("children", []): self.add_children_gui(child_data) + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + def add_children_gui(self, child_configuration): item_type = child_configuration["type"] # Pop label to not be set in child From 6efbecff8f49eab23d5a6d5ee84271519320290b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 13:59:54 +0200 Subject: [PATCH 760/947] added bottom margin to content when should highligh content --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index bd046e32dd..908fe8e070 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1573,8 +1573,10 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): if input_data.get("highlight_content", False): content_state = "hightlighted" + bottom_margin = 5 else: content_state = "" + bottom_margin = 0 self.input_fields = [] @@ -1592,7 +1594,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): content_widget.setObjectName("ContentWidget") content_widget.setProperty("content_state", content_state) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, bottom_margin) body_widget.set_content_widget(content_widget) From 967e312df0861751347e3b0b2d2ad2ab4ca20941 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:11:48 +0200 Subject: [PATCH 761/947] AnatomyWIdget is using expanding widget --- .../config_setting/widgets/anatomy_inputs.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 9e69d7234e..befd928463 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,7 +1,7 @@ from Qt import QtWidgets, QtCore from .widgets import ExpandingWidget from .inputs import ConfigObject, ModifiableDict, PathWidget -from .lib import NOT_SET, TypeToKlass +from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET class AnatomyWidget(QtWidgets.QWidget, ConfigObject): @@ -56,13 +56,13 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): body_widget = ExpandingWidget("Anatomy", self) layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 5, 0, 5) + layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(body_widget) content_widget = QtWidgets.QWidget(body_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) content_layout.setSpacing(5) content_layout.addWidget(self.root_widget) @@ -206,12 +206,18 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): checkbox_layout.addWidget(multiroot_label, 0) checkbox_layout.addWidget(multiroot_checkbox, 1) + body_widget = ExpandingWidget("Roots", self) + content_widget = QtWidgets.QWidget(body_widget) + path_widget_data = { "key": "roots", "multipath": False, "multiplatform": True } - singleroot_widget = PathWidget(path_widget_data, self, as_widget=True) + singleroot_widget = PathWidget( + path_widget_data, self, + as_widget=True, parent_widget=content_widget + ) multiroot_data = { "key": "roots", "object_type": "path-widget", @@ -220,12 +226,13 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): "multiplatform": True } } - multiroot_widget = ModifiableDict(multiroot_data, self, as_widget=True) + multiroot_widget = ModifiableDict( + multiroot_data, self, + as_widget=True, parent_widget=content_widget + ) - body_widget = ExpandingWidget("Roots", self) - - content_widget = QtWidgets.QWidget(body_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) content_layout.addWidget(checkbox_widget) content_layout.addWidget(singleroot_widget) content_layout.addWidget(multiroot_widget) From 4e03a7c6a55a8884b2ed62b64bcadfa11d017a65 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:12:10 +0200 Subject: [PATCH 762/947] lowered bg color increasing --- pype/tools/config_setting/config_setting/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index af61655a36..fe3bba366a 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -127,7 +127,7 @@ QPushButton[btn-type="expand-toggle"] { background-color: transparent; } #ContentWidget[content_state="hightlighted"] { - background-color: rgba(19, 26, 32, 20%); + background-color: rgba(19, 26, 32, 15%); } #SideLineWidget { From 99899a35ea7caca510f883de70ef9452d02b2299 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:18:40 +0200 Subject: [PATCH 763/947] fix style updates in list --- .../config_setting/config_setting/widgets/inputs.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 908fe8e070..18a6ef690b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -952,6 +952,9 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def child_overriden(self): return self.value_input.child_overriden + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + def mouseReleaseEvent(self, event): return QtWidgets.QWidget.mouseReleaseEvent(self, event) @@ -1047,6 +1050,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.start_value = self.item_value() self._is_modified = self.global_value != self.start_value + self.hierarchical_style_update() def set_value(self, value): previous_inputs = tuple(self.input_fields) @@ -1102,7 +1106,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): # else (when add button clicked) trigger `_on_value_change` if value is not None: item_widget.value_input.update_global_values(value) - self.hierarchical_style_update() else: self._on_value_change() self.updateGeometry() @@ -1143,6 +1146,11 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.set_value(value) + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + def update_style(self): state = self.style_state( self.is_invalid, self.is_overriden, self.is_modified From 6c37ede07cb9a12a99f4282cc566d43632ebca47 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:40:08 +0200 Subject: [PATCH 764/947] fix raw json in-validation --- pype/tools/config_setting/config_setting/widgets/inputs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 18a6ef690b..a08ad88015 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -820,10 +820,11 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): elif self.default_value is not NOT_SET: self.set_value(self.default_value) + self._is_invalid = self.text_input.has_invalid_value() + self.global_value = value self.start_value = self.item_value() - self._is_invalid = self.text_input.has_invalid_value() self._is_modified = self.global_value != self.start_value def set_value(self, value): From 1ceb51a26b3659b95cb517f1a971eb99d6de07ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:43:17 +0200 Subject: [PATCH 765/947] added simple implementation of Templates --- .../config_setting/widgets/anatomy_inputs.py | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index befd928463..7705efd91c 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,6 +1,6 @@ from Qt import QtWidgets, QtCore from .widgets import ExpandingWidget -from .inputs import ConfigObject, ModifiableDict, PathWidget +from .inputs import ConfigObject, ModifiableDict, PathWidget, RawJsonWidget from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET @@ -172,6 +172,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): def item_value(self): output = {} output.update(self.root_widget.config_value()) + output.update(self.templates_widget.config_value()) return output def config_value(self): @@ -459,18 +460,30 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): return {self.key: self.item_value()} -# TODO implement -class TemplatesWidget(QtWidgets.QWidget): - def __init__(self, parent=None): +class TemplatesWidget(QtWidgets.QWidget, ConfigObject): + def __init__(self, parent): super(TemplatesWidget, self).__init__(parent) + self._parent = parent + + self._is_group = True + self.any_parent_is_group = False + self.key = "templates" + body_widget = ExpandingWidget("Templates", self) content_widget = QtWidgets.QWidget(body_widget) body_widget.set_content_widget(content_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - label = QtWidgets.QLabel("Nothing yet", content_widget) - content_layout.addWidget(label) + template_input_data = { + "key": self.key + } + self.label_widget = body_widget.label_widget + self.value_input = RawJsonWidget( + template_input_data, self, + label_widget=self.label_widget + ) + content_layout.addWidget(self.value_input) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -479,39 +492,50 @@ class TemplatesWidget(QtWidgets.QWidget): layout.addWidget(body_widget) def update_global_values(self, values): - pass + self.value_input.update_global_values(values) def apply_overrides(self, parent_values): - pass + self.value_input.apply_overrides(parent_values) def hierarchical_style_update(self): - pass + self.value_input.hierarchical_style_update() @property def is_modified(self): - return False + return self.value_input.is_modified @property def is_overriden(self): - return False + return self._is_overriden @property def child_modified(self): - return False + return self.value_input.child_modified @property def child_overriden(self): - return False + return self.value_input.child_overriden @property def child_invalid(self): - return False + return self.value_input.child_invalid def remove_overrides(self): - pass + print("* `remove_overrides` NOT IMPLEMENTED") def discard_changes(self): - pass + print("* `discard_changes` NOT IMPLEMENTED") + + def overrides(self): + if not self.is_overriden: + return NOT_SET, False + return self.config_value(), True + + def item_value(self): + return self.value_input.item_value() + + def config_value(self): + return self.value_input.config_value() TypeToKlass.types["anatomy"] = AnatomyWidget From 80ef4bf876ccf60d0b7c1093991321d002a1d6ef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:49:13 +0200 Subject: [PATCH 766/947] implemented set_as_overriden for path widget --- pype/tools/config_setting/config_setting/widgets/inputs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index a08ad88015..155bcbb74d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2243,6 +2243,9 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified + def set_as_overriden(self): + self._is_overriden = True + @property def child_modified(self): for input_field in self.input_fields: From d7e2b07fcc38bbdf025a9d5db529a6bb0c5011d6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 14:57:53 +0200 Subject: [PATCH 767/947] few minor changes about attribute values in anatomy iwdgets --- .../config_setting/widgets/anatomy_inputs.py | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 7705efd91c..688b4bf715 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -131,10 +131,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.hierarchical_style_update() self.update_style() - @property - def is_modified(self): - return self._is_modified or self.child_modified - @property def child_modified(self): return ( @@ -363,9 +359,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if self.ignore_value_changes: return - if item is None: - pass - elif ( + if item is not None and ( (self.is_multiroot and item != self.multiroot_widget) or (not self.is_multiroot and item != self.singleroot_widget) ): @@ -393,14 +387,6 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._on_value_change() - @property - def is_modified(self): - return self._is_modified or self.child_modified - - @property - def is_overriden(self): - return self._is_overriden - @property def child_modified(self): if self.is_multiroot: @@ -450,6 +436,11 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified + def set_as_overriden(self): + self._is_overriden = self._was_overriden + self.singleroot_widget.set_as_overriden() + self.multiroot_widget.set_as_overriden() + def item_value(self): if self.is_multiroot: return self.multiroot_widget.item_value() @@ -521,13 +512,16 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): return self.value_input.child_invalid def remove_overrides(self): - print("* `remove_overrides` NOT IMPLEMENTED") + self.value_input.remove_overrides() def discard_changes(self): - print("* `discard_changes` NOT IMPLEMENTED") + self.value_input.discard_changes() + + def set_as_overriden(self): + self.value_input.set_as_overriden() def overrides(self): - if not self.is_overriden: + if not self.child_overriden: return NOT_SET, False return self.config_value(), True From 427a6109b7ad5d1b7591b60a50e9ba6d6df78dd3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 15:09:42 +0200 Subject: [PATCH 768/947] removed all io_nonsingleton files from pype --- .../adobe_communicator/lib/io_nonsingleton.py | 460 ------------------ pype/modules/ftrack/lib/io_nonsingleton.py | 460 ------------------ 2 files changed, 920 deletions(-) delete mode 100644 pype/modules/adobe_communicator/lib/io_nonsingleton.py delete mode 100644 pype/modules/ftrack/lib/io_nonsingleton.py diff --git a/pype/modules/adobe_communicator/lib/io_nonsingleton.py b/pype/modules/adobe_communicator/lib/io_nonsingleton.py deleted file mode 100644 index da37c657c6..0000000000 --- a/pype/modules/adobe_communicator/lib/io_nonsingleton.py +++ /dev/null @@ -1,460 +0,0 @@ -""" -Wrapper around interactions with the database - -Copy of io module in avalon-core. - - In this case not working as singleton with api.Session! -""" - -import os -import time -import errno -import shutil -import logging -import tempfile -import functools -import contextlib - -from avalon import schema -from avalon.vendor import requests -from avalon.io import extract_port_from_url - -# Third-party dependencies -import pymongo - - -def auto_reconnect(func): - """Handling auto reconnect in 3 retry times""" - @functools.wraps(func) - def decorated(*args, **kwargs): - object = args[0] - for retry in range(3): - try: - return func(*args, **kwargs) - except pymongo.errors.AutoReconnect: - object.log.error("Reconnecting..") - time.sleep(0.1) - else: - raise - - return decorated - - -class DbConnector(object): - - log = logging.getLogger(__name__) - - def __init__(self): - self.Session = {} - self._mongo_client = None - self._sentry_client = None - self._sentry_logging_handler = None - self._database = None - self._is_installed = False - - def __getitem__(self, key): - # gives direct access to collection withou setting `active_table` - return self._database[key] - - def __getattribute__(self, attr): - # not all methods of PyMongo database are implemented with this it is - # possible to use them too - try: - return super(DbConnector, self).__getattribute__(attr) - except AttributeError: - cur_proj = self.Session["AVALON_PROJECT"] - return self._database[cur_proj].__getattribute__(attr) - - def install(self): - """Establish a persistent connection to the database""" - if self._is_installed: - return - - logging.basicConfig() - self.Session.update(self._from_environment()) - - timeout = int(self.Session["AVALON_TIMEOUT"]) - mongo_url = self.Session["AVALON_MONGO"] - kwargs = { - "host": mongo_url, - "serverSelectionTimeoutMS": timeout - } - - port = extract_port_from_url(mongo_url) - if port is not None: - kwargs["port"] = int(port) - - self._mongo_client = pymongo.MongoClient(**kwargs) - - for retry in range(3): - try: - t1 = time.time() - self._mongo_client.server_info() - - except Exception: - self.log.error("Retrying..") - time.sleep(1) - timeout *= 1.5 - - else: - break - - else: - raise IOError( - "ERROR: Couldn't connect to %s in " - "less than %.3f ms" % (self.Session["AVALON_MONGO"], timeout)) - - self.log.info("Connected to %s, delay %.3f s" % ( - self.Session["AVALON_MONGO"], time.time() - t1)) - - self._install_sentry() - - self._database = self._mongo_client[self.Session["AVALON_DB"]] - self._is_installed = True - - def _install_sentry(self): - if "AVALON_SENTRY" not in self.Session: - return - - try: - from raven import Client - from raven.handlers.logging import SentryHandler - from raven.conf import setup_logging - except ImportError: - # Note: There was a Sentry address in this Session - return self.log.warning("Sentry disabled, raven not installed") - - client = Client(self.Session["AVALON_SENTRY"]) - - # Transmit log messages to Sentry - handler = SentryHandler(client) - handler.setLevel(logging.WARNING) - - setup_logging(handler) - - self._sentry_client = client - self._sentry_logging_handler = handler - self.log.info( - "Connected to Sentry @ %s" % self.Session["AVALON_SENTRY"] - ) - - def _from_environment(self): - Session = { - item[0]: os.getenv(item[0], item[1]) - for item in ( - # Root directory of projects on disk - ("AVALON_PROJECTS", None), - - # Name of current Project - ("AVALON_PROJECT", ""), - - # Name of current Asset - ("AVALON_ASSET", ""), - - # Name of current silo - ("AVALON_SILO", ""), - - # Name of current task - ("AVALON_TASK", None), - - # Name of current app - ("AVALON_APP", None), - - # Path to working directory - ("AVALON_WORKDIR", None), - - # Name of current Config - # TODO(marcus): Establish a suitable default config - ("AVALON_CONFIG", "no_config"), - - # Name of Avalon in graphical user interfaces - # Use this to customise the visual appearance of Avalon - # to better integrate with your surrounding pipeline - ("AVALON_LABEL", "Avalon"), - - # Used during any connections to the outside world - ("AVALON_TIMEOUT", "1000"), - - # Address to Asset Database - ("AVALON_MONGO", "mongodb://localhost:27017"), - - # Name of database used in MongoDB - ("AVALON_DB", "avalon"), - - # Address to Sentry - ("AVALON_SENTRY", None), - - # Address to Deadline Web Service - # E.g. http://192.167.0.1:8082 - ("AVALON_DEADLINE", None), - - # Enable features not necessarily stable. The user's own risk - ("AVALON_EARLY_ADOPTER", None), - - # Address of central asset repository, contains - # the following interface: - # /upload - # /download - # /manager (optional) - ("AVALON_LOCATION", "http://127.0.0.1"), - - # Boolean of whether to upload published material - # to central asset repository - ("AVALON_UPLOAD", None), - - # Generic username and password - ("AVALON_USERNAME", "avalon"), - ("AVALON_PASSWORD", "secret"), - - # Unique identifier for instances in working files - ("AVALON_INSTANCE_ID", "avalon.instance"), - ("AVALON_CONTAINER_ID", "avalon.container"), - - # Enable debugging - ("AVALON_DEBUG", None), - - ) if os.getenv(item[0], item[1]) is not None - } - - Session["schema"] = "avalon-core:session-2.0" - try: - schema.validate(Session) - except schema.ValidationError as e: - # TODO(marcus): Make this mandatory - self.log.warning(e) - - return Session - - def uninstall(self): - """Close any connection to the database""" - try: - self._mongo_client.close() - except AttributeError: - pass - - self._mongo_client = None - self._database = None - self._is_installed = False - - def active_project(self): - """Return the name of the active project""" - return self.Session["AVALON_PROJECT"] - - def activate_project(self, project_name): - self.Session["AVALON_PROJECT"] = project_name - - def projects(self): - """List available projects - - Returns: - list of project documents - - """ - - collection_names = self.collections() - for project in collection_names: - if project in ("system.indexes",): - continue - - # Each collection will have exactly one project document - document = self.find_project(project) - - if document is not None: - yield document - - def locate(self, path): - """Traverse a hierarchy from top-to-bottom - - Example: - representation = locate(["hulk", "Bruce", "modelDefault", 1, "ma"]) - - Returns: - representation (ObjectId) - - """ - - components = zip( - ("project", "asset", "subset", "version", "representation"), - path - ) - - parent = None - for type_, name in components: - latest = (type_ == "version") and name in (None, -1) - - try: - if latest: - parent = self.find_one( - filter={ - "type": type_, - "parent": parent - }, - projection={"_id": 1}, - sort=[("name", -1)] - )["_id"] - else: - parent = self.find_one( - filter={ - "type": type_, - "name": name, - "parent": parent - }, - projection={"_id": 1}, - )["_id"] - - except TypeError: - return None - - return parent - - @auto_reconnect - def collections(self): - return self._database.collection_names() - - @auto_reconnect - def find_project(self, project): - return self._database[project].find_one({"type": "project"}) - - @auto_reconnect - def insert_one(self, item): - assert isinstance(item, dict), "item must be of type " - schema.validate(item) - return self._database[self.Session["AVALON_PROJECT"]].insert_one(item) - - @auto_reconnect - def insert_many(self, items, ordered=True): - # check if all items are valid - assert isinstance(items, list), "`items` must be of type " - for item in items: - assert isinstance(item, dict), "`item` must be of type " - schema.validate(item) - - return self._database[self.Session["AVALON_PROJECT"]].insert_many( - items, - ordered=ordered) - - @auto_reconnect - def find(self, filter, projection=None, sort=None): - return self._database[self.Session["AVALON_PROJECT"]].find( - filter=filter, - projection=projection, - sort=sort - ) - - @auto_reconnect - def find_one(self, filter, projection=None, sort=None): - assert isinstance(filter, dict), "filter must be " - - return self._database[self.Session["AVALON_PROJECT"]].find_one( - filter=filter, - projection=projection, - sort=sort - ) - - @auto_reconnect - def save(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].save( - *args, **kwargs) - - @auto_reconnect - def replace_one(self, filter, replacement): - return self._database[self.Session["AVALON_PROJECT"]].replace_one( - filter, replacement) - - @auto_reconnect - def update_many(self, filter, update): - return self._database[self.Session["AVALON_PROJECT"]].update_many( - filter, update) - - @auto_reconnect - def distinct(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].distinct( - *args, **kwargs) - - @auto_reconnect - def drop(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].drop( - *args, **kwargs) - - @auto_reconnect - def delete_many(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].delete_many( - *args, **kwargs) - - def parenthood(self, document): - assert document is not None, "This is a bug" - - parents = list() - - while document.get("parent") is not None: - document = self.find_one({"_id": document["parent"]}) - - if document is None: - break - - if document.get("type") == "master_version": - _document = self.find_one({"_id": document["version_id"]}) - document["data"] = _document["data"] - - parents.append(document) - - return parents - - @contextlib.contextmanager - def tempdir(self): - tempdir = tempfile.mkdtemp() - try: - yield tempdir - finally: - shutil.rmtree(tempdir) - - def download(self, src, dst): - """Download `src` to `dst` - - Arguments: - src (str): URL to source file - dst (str): Absolute path to destination file - - Yields tuple (progress, error): - progress (int): Between 0-100 - error (Exception): Any exception raised when first making connection - - """ - - try: - response = requests.get( - src, - stream=True, - auth=requests.auth.HTTPBasicAuth( - self.Session["AVALON_USERNAME"], - self.Session["AVALON_PASSWORD"] - ) - ) - except requests.ConnectionError as e: - yield None, e - return - - with self.tempdir() as dirname: - tmp = os.path.join(dirname, os.path.basename(src)) - - with open(tmp, "wb") as f: - total_length = response.headers.get("content-length") - - if total_length is None: # no content length header - f.write(response.content) - else: - downloaded = 0 - total_length = int(total_length) - for data in response.iter_content(chunk_size=4096): - downloaded += len(data) - f.write(data) - - yield int(100.0 * downloaded / total_length), None - - try: - os.makedirs(os.path.dirname(dst)) - except OSError as e: - # An already existing destination directory is fine. - if e.errno != errno.EEXIST: - raise - - shutil.copy(tmp, dst) diff --git a/pype/modules/ftrack/lib/io_nonsingleton.py b/pype/modules/ftrack/lib/io_nonsingleton.py deleted file mode 100644 index da37c657c6..0000000000 --- a/pype/modules/ftrack/lib/io_nonsingleton.py +++ /dev/null @@ -1,460 +0,0 @@ -""" -Wrapper around interactions with the database - -Copy of io module in avalon-core. - - In this case not working as singleton with api.Session! -""" - -import os -import time -import errno -import shutil -import logging -import tempfile -import functools -import contextlib - -from avalon import schema -from avalon.vendor import requests -from avalon.io import extract_port_from_url - -# Third-party dependencies -import pymongo - - -def auto_reconnect(func): - """Handling auto reconnect in 3 retry times""" - @functools.wraps(func) - def decorated(*args, **kwargs): - object = args[0] - for retry in range(3): - try: - return func(*args, **kwargs) - except pymongo.errors.AutoReconnect: - object.log.error("Reconnecting..") - time.sleep(0.1) - else: - raise - - return decorated - - -class DbConnector(object): - - log = logging.getLogger(__name__) - - def __init__(self): - self.Session = {} - self._mongo_client = None - self._sentry_client = None - self._sentry_logging_handler = None - self._database = None - self._is_installed = False - - def __getitem__(self, key): - # gives direct access to collection withou setting `active_table` - return self._database[key] - - def __getattribute__(self, attr): - # not all methods of PyMongo database are implemented with this it is - # possible to use them too - try: - return super(DbConnector, self).__getattribute__(attr) - except AttributeError: - cur_proj = self.Session["AVALON_PROJECT"] - return self._database[cur_proj].__getattribute__(attr) - - def install(self): - """Establish a persistent connection to the database""" - if self._is_installed: - return - - logging.basicConfig() - self.Session.update(self._from_environment()) - - timeout = int(self.Session["AVALON_TIMEOUT"]) - mongo_url = self.Session["AVALON_MONGO"] - kwargs = { - "host": mongo_url, - "serverSelectionTimeoutMS": timeout - } - - port = extract_port_from_url(mongo_url) - if port is not None: - kwargs["port"] = int(port) - - self._mongo_client = pymongo.MongoClient(**kwargs) - - for retry in range(3): - try: - t1 = time.time() - self._mongo_client.server_info() - - except Exception: - self.log.error("Retrying..") - time.sleep(1) - timeout *= 1.5 - - else: - break - - else: - raise IOError( - "ERROR: Couldn't connect to %s in " - "less than %.3f ms" % (self.Session["AVALON_MONGO"], timeout)) - - self.log.info("Connected to %s, delay %.3f s" % ( - self.Session["AVALON_MONGO"], time.time() - t1)) - - self._install_sentry() - - self._database = self._mongo_client[self.Session["AVALON_DB"]] - self._is_installed = True - - def _install_sentry(self): - if "AVALON_SENTRY" not in self.Session: - return - - try: - from raven import Client - from raven.handlers.logging import SentryHandler - from raven.conf import setup_logging - except ImportError: - # Note: There was a Sentry address in this Session - return self.log.warning("Sentry disabled, raven not installed") - - client = Client(self.Session["AVALON_SENTRY"]) - - # Transmit log messages to Sentry - handler = SentryHandler(client) - handler.setLevel(logging.WARNING) - - setup_logging(handler) - - self._sentry_client = client - self._sentry_logging_handler = handler - self.log.info( - "Connected to Sentry @ %s" % self.Session["AVALON_SENTRY"] - ) - - def _from_environment(self): - Session = { - item[0]: os.getenv(item[0], item[1]) - for item in ( - # Root directory of projects on disk - ("AVALON_PROJECTS", None), - - # Name of current Project - ("AVALON_PROJECT", ""), - - # Name of current Asset - ("AVALON_ASSET", ""), - - # Name of current silo - ("AVALON_SILO", ""), - - # Name of current task - ("AVALON_TASK", None), - - # Name of current app - ("AVALON_APP", None), - - # Path to working directory - ("AVALON_WORKDIR", None), - - # Name of current Config - # TODO(marcus): Establish a suitable default config - ("AVALON_CONFIG", "no_config"), - - # Name of Avalon in graphical user interfaces - # Use this to customise the visual appearance of Avalon - # to better integrate with your surrounding pipeline - ("AVALON_LABEL", "Avalon"), - - # Used during any connections to the outside world - ("AVALON_TIMEOUT", "1000"), - - # Address to Asset Database - ("AVALON_MONGO", "mongodb://localhost:27017"), - - # Name of database used in MongoDB - ("AVALON_DB", "avalon"), - - # Address to Sentry - ("AVALON_SENTRY", None), - - # Address to Deadline Web Service - # E.g. http://192.167.0.1:8082 - ("AVALON_DEADLINE", None), - - # Enable features not necessarily stable. The user's own risk - ("AVALON_EARLY_ADOPTER", None), - - # Address of central asset repository, contains - # the following interface: - # /upload - # /download - # /manager (optional) - ("AVALON_LOCATION", "http://127.0.0.1"), - - # Boolean of whether to upload published material - # to central asset repository - ("AVALON_UPLOAD", None), - - # Generic username and password - ("AVALON_USERNAME", "avalon"), - ("AVALON_PASSWORD", "secret"), - - # Unique identifier for instances in working files - ("AVALON_INSTANCE_ID", "avalon.instance"), - ("AVALON_CONTAINER_ID", "avalon.container"), - - # Enable debugging - ("AVALON_DEBUG", None), - - ) if os.getenv(item[0], item[1]) is not None - } - - Session["schema"] = "avalon-core:session-2.0" - try: - schema.validate(Session) - except schema.ValidationError as e: - # TODO(marcus): Make this mandatory - self.log.warning(e) - - return Session - - def uninstall(self): - """Close any connection to the database""" - try: - self._mongo_client.close() - except AttributeError: - pass - - self._mongo_client = None - self._database = None - self._is_installed = False - - def active_project(self): - """Return the name of the active project""" - return self.Session["AVALON_PROJECT"] - - def activate_project(self, project_name): - self.Session["AVALON_PROJECT"] = project_name - - def projects(self): - """List available projects - - Returns: - list of project documents - - """ - - collection_names = self.collections() - for project in collection_names: - if project in ("system.indexes",): - continue - - # Each collection will have exactly one project document - document = self.find_project(project) - - if document is not None: - yield document - - def locate(self, path): - """Traverse a hierarchy from top-to-bottom - - Example: - representation = locate(["hulk", "Bruce", "modelDefault", 1, "ma"]) - - Returns: - representation (ObjectId) - - """ - - components = zip( - ("project", "asset", "subset", "version", "representation"), - path - ) - - parent = None - for type_, name in components: - latest = (type_ == "version") and name in (None, -1) - - try: - if latest: - parent = self.find_one( - filter={ - "type": type_, - "parent": parent - }, - projection={"_id": 1}, - sort=[("name", -1)] - )["_id"] - else: - parent = self.find_one( - filter={ - "type": type_, - "name": name, - "parent": parent - }, - projection={"_id": 1}, - )["_id"] - - except TypeError: - return None - - return parent - - @auto_reconnect - def collections(self): - return self._database.collection_names() - - @auto_reconnect - def find_project(self, project): - return self._database[project].find_one({"type": "project"}) - - @auto_reconnect - def insert_one(self, item): - assert isinstance(item, dict), "item must be of type " - schema.validate(item) - return self._database[self.Session["AVALON_PROJECT"]].insert_one(item) - - @auto_reconnect - def insert_many(self, items, ordered=True): - # check if all items are valid - assert isinstance(items, list), "`items` must be of type " - for item in items: - assert isinstance(item, dict), "`item` must be of type " - schema.validate(item) - - return self._database[self.Session["AVALON_PROJECT"]].insert_many( - items, - ordered=ordered) - - @auto_reconnect - def find(self, filter, projection=None, sort=None): - return self._database[self.Session["AVALON_PROJECT"]].find( - filter=filter, - projection=projection, - sort=sort - ) - - @auto_reconnect - def find_one(self, filter, projection=None, sort=None): - assert isinstance(filter, dict), "filter must be " - - return self._database[self.Session["AVALON_PROJECT"]].find_one( - filter=filter, - projection=projection, - sort=sort - ) - - @auto_reconnect - def save(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].save( - *args, **kwargs) - - @auto_reconnect - def replace_one(self, filter, replacement): - return self._database[self.Session["AVALON_PROJECT"]].replace_one( - filter, replacement) - - @auto_reconnect - def update_many(self, filter, update): - return self._database[self.Session["AVALON_PROJECT"]].update_many( - filter, update) - - @auto_reconnect - def distinct(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].distinct( - *args, **kwargs) - - @auto_reconnect - def drop(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].drop( - *args, **kwargs) - - @auto_reconnect - def delete_many(self, *args, **kwargs): - return self._database[self.Session["AVALON_PROJECT"]].delete_many( - *args, **kwargs) - - def parenthood(self, document): - assert document is not None, "This is a bug" - - parents = list() - - while document.get("parent") is not None: - document = self.find_one({"_id": document["parent"]}) - - if document is None: - break - - if document.get("type") == "master_version": - _document = self.find_one({"_id": document["version_id"]}) - document["data"] = _document["data"] - - parents.append(document) - - return parents - - @contextlib.contextmanager - def tempdir(self): - tempdir = tempfile.mkdtemp() - try: - yield tempdir - finally: - shutil.rmtree(tempdir) - - def download(self, src, dst): - """Download `src` to `dst` - - Arguments: - src (str): URL to source file - dst (str): Absolute path to destination file - - Yields tuple (progress, error): - progress (int): Between 0-100 - error (Exception): Any exception raised when first making connection - - """ - - try: - response = requests.get( - src, - stream=True, - auth=requests.auth.HTTPBasicAuth( - self.Session["AVALON_USERNAME"], - self.Session["AVALON_PASSWORD"] - ) - ) - except requests.ConnectionError as e: - yield None, e - return - - with self.tempdir() as dirname: - tmp = os.path.join(dirname, os.path.basename(src)) - - with open(tmp, "wb") as f: - total_length = response.headers.get("content-length") - - if total_length is None: # no content length header - f.write(response.content) - else: - downloaded = 0 - total_length = int(total_length) - for data in response.iter_content(chunk_size=4096): - downloaded += len(data) - f.write(data) - - yield int(100.0 * downloaded / total_length), None - - try: - os.makedirs(os.path.dirname(dst)) - except OSError as e: - # An already existing destination directory is fine. - if e.errno != errno.EEXIST: - raise - - shutil.copy(tmp, dst) From c68423279c0c1b7dd5a9af4be3d3f42b5d0561da Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 15:10:34 +0200 Subject: [PATCH 769/947] everywhere where io_nonsingleton was used is used AvalonmongoDB now --- pype/modules/adobe_communicator/lib/__init__.py | 2 -- pype/modules/adobe_communicator/lib/rest_api.py | 4 ++-- pype/modules/avalon_apps/rest_api.py | 4 ++-- pype/modules/ftrack/actions/action_delete_asset.py | 4 ++-- pype/modules/ftrack/actions/action_delete_old_versions.py | 4 ++-- pype/modules/ftrack/actions/action_delivery.py | 4 ++-- .../ftrack/actions/action_store_thumbnails_to_avalon.py | 4 ++-- pype/modules/ftrack/events/event_sync_to_avalon.py | 4 ++-- pype/modules/ftrack/events/event_user_assigment.py | 4 ++-- pype/modules/ftrack/lib/avalon_sync.py | 4 ++-- pype/tools/launcher/window.py | 4 ++-- pype/tools/standalonepublish/app.py | 4 ++-- 12 files changed, 22 insertions(+), 24 deletions(-) diff --git a/pype/modules/adobe_communicator/lib/__init__.py b/pype/modules/adobe_communicator/lib/__init__.py index 23aee81275..f918e49a60 100644 --- a/pype/modules/adobe_communicator/lib/__init__.py +++ b/pype/modules/adobe_communicator/lib/__init__.py @@ -1,8 +1,6 @@ -from .io_nonsingleton import DbConnector from .rest_api import AdobeRestApi, PUBLISH_PATHS __all__ = [ "PUBLISH_PATHS", - "DbConnector", "AdobeRestApi" ] diff --git a/pype/modules/adobe_communicator/lib/rest_api.py b/pype/modules/adobe_communicator/lib/rest_api.py index 86739e4d80..35094d10dc 100644 --- a/pype/modules/adobe_communicator/lib/rest_api.py +++ b/pype/modules/adobe_communicator/lib/rest_api.py @@ -2,7 +2,7 @@ import os import sys import copy from pype.modules.rest_api import RestApi, route, abort, CallbackResult -from .io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB from pype.api import config, execute, Logger log = Logger().get_logger("AdobeCommunicator") @@ -14,7 +14,7 @@ PUBLISH_PATHS = [] class AdobeRestApi(RestApi): - dbcon = DbConnector() + dbcon = AvalonMongoDB() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/pype/modules/avalon_apps/rest_api.py b/pype/modules/avalon_apps/rest_api.py index 1cb9e544a7..2408e56bbc 100644 --- a/pype/modules/avalon_apps/rest_api.py +++ b/pype/modules/avalon_apps/rest_api.py @@ -4,14 +4,14 @@ import json import bson import bson.json_util from pype.modules.rest_api import RestApi, abort, CallbackResult -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB class AvalonRestApi(RestApi): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.dbcon = DbConnector() + self.dbcon = AvalonMongoDB() self.dbcon.install() @RestApi.route("/projects/", url_prefix="/avalon", methods="GET") diff --git a/pype/modules/ftrack/actions/action_delete_asset.py b/pype/modules/ftrack/actions/action_delete_asset.py index 27394770e1..7d2dac3320 100644 --- a/pype/modules/ftrack/actions/action_delete_asset.py +++ b/pype/modules/ftrack/actions/action_delete_asset.py @@ -5,7 +5,7 @@ from queue import Queue from bson.objectid import ObjectId from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB class DeleteAssetSubset(BaseAction): @@ -21,7 +21,7 @@ class DeleteAssetSubset(BaseAction): #: roles that are allowed to register this action role_list = ["Pypeclub", "Administrator", "Project Manager"] #: Db connection - dbcon = DbConnector() + dbcon = AvalonMongoDB() splitter = {"type": "label", "value": "---"} action_data_by_id = {} diff --git a/pype/modules/ftrack/actions/action_delete_old_versions.py b/pype/modules/ftrack/actions/action_delete_old_versions.py index 6a4c5a0cae..b55f091fdc 100644 --- a/pype/modules/ftrack/actions/action_delete_old_versions.py +++ b/pype/modules/ftrack/actions/action_delete_old_versions.py @@ -6,7 +6,7 @@ import clique from pymongo import UpdateOne from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB from pype.api import Anatomy import avalon.pipeline @@ -24,7 +24,7 @@ class DeleteOldVersions(BaseAction): role_list = ["Pypeclub", "Project Manager", "Administrator"] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") - dbcon = DbConnector() + dbcon = AvalonMongoDB() inteface_title = "Choose your preferences" splitter_item = {"type": "label", "value": "---"} diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index cf80fd77ff..8812ce9bc7 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -13,7 +13,7 @@ from avalon.vendor import filelink from pype.api import Anatomy, config from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB class Delivery(BaseAction): @@ -24,7 +24,7 @@ class Delivery(BaseAction): role_list = ["Pypeclub", "Administrator", "Project manager"] icon = statics_icon("ftrack", "action_icons", "Delivery.svg") - db_con = DbConnector() + db_con = AvalonMongoDB() def discover(self, session, entities, event): for entity in entities: diff --git a/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py b/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py index 94ca503233..36f7175768 100644 --- a/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py +++ b/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py @@ -6,7 +6,7 @@ import json from bson.objectid import ObjectId from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.api import Anatomy -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY @@ -25,7 +25,7 @@ class StoreThumbnailsToAvalon(BaseAction): icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") thumbnail_key = "AVALON_THUMBNAIL_ROOT" - db_con = DbConnector() + db_con = AvalonMongoDB() def discover(self, session, entities, event): for entity in entities: diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index efcb74a608..314871f5b3 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -19,12 +19,12 @@ from pype.modules.ftrack.lib.avalon_sync import ( import ftrack_api from pype.modules.ftrack import BaseEvent -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB class SyncToAvalonEvent(BaseEvent): - dbcon = DbConnector() + dbcon = AvalonMongoDB() interest_entTypes = ["show", "task"] ignore_ent_types = ["Milestone"] diff --git a/pype/modules/ftrack/events/event_user_assigment.py b/pype/modules/ftrack/events/event_user_assigment.py index d1b3439c8f..19a67b745f 100644 --- a/pype/modules/ftrack/events/event_user_assigment.py +++ b/pype/modules/ftrack/events/event_user_assigment.py @@ -4,7 +4,7 @@ import subprocess from pype.modules.ftrack import BaseEvent from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB from bson.objectid import ObjectId @@ -37,7 +37,7 @@ class UserAssigmentEvent(BaseEvent): 3) path to publish files of task user was (de)assigned to """ - db_con = DbConnector() + db_con = AvalonMongoDB() def error(self, *err): for e in err: diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 4bab1676d4..65a59452da 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -5,7 +5,7 @@ import json import collections import copy -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB import avalon import avalon.api @@ -240,7 +240,7 @@ def get_hierarchical_attributes(session, entity, attr_names, attr_defaults={}): class SyncEntitiesFactory: - dbcon = DbConnector() + dbcon = AvalonMongoDB() project_query = ( "select full_name, name, custom_attributes" diff --git a/pype/tools/launcher/window.py b/pype/tools/launcher/window.py index 13b4abee6e..7c680a927b 100644 --- a/pype/tools/launcher/window.py +++ b/pype/tools/launcher/window.py @@ -4,7 +4,7 @@ import logging from Qt import QtWidgets, QtCore, QtGui from avalon import style -from pype.modules.ftrack.lib.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB from pype.api import resources from avalon.tools import lib as tools_lib @@ -251,7 +251,7 @@ class LauncherWindow(QtWidgets.QDialog): self.log = logging.getLogger( ".".join([__name__, self.__class__.__name__]) ) - self.dbcon = DbConnector() + self.dbcon = AvalonMongoDB() self.setWindowTitle("Launcher") self.setFocusPolicy(QtCore.Qt.StrongFocus) diff --git a/pype/tools/standalonepublish/app.py b/pype/tools/standalonepublish/app.py index d139366a1c..feba46987f 100644 --- a/pype/tools/standalonepublish/app.py +++ b/pype/tools/standalonepublish/app.py @@ -1,7 +1,7 @@ from bson.objectid import ObjectId from Qt import QtWidgets, QtCore from widgets import AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget -from avalon.tools.libraryloader.io_nonsingleton import DbConnector +from avalon.api import AvalonMongoDB class Window(QtWidgets.QDialog): @@ -10,7 +10,7 @@ class Window(QtWidgets.QDialog): :param parent: Main widget that cares about all GUIs :type parent: QtWidgets.QMainWindow """ - _db = DbConnector() + _db = AvalonMongoDB() _jobs = {} valid_family = False valid_components = False From 019ed41e23ac3b9de57e865cf8645219fdb5a5f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 16:08:54 +0200 Subject: [PATCH 770/947] overrides for anatomy are saving data --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 688b4bf715..b0aed18e01 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -161,9 +161,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.discard_changes() def overrides(self): - if self.is_overriden: - return self.config_value(), True - return {self.key: {}}, True + return self.config_value(), True def item_value(self): output = {} From 73dfecbf1a9f0518e0bd15fb7cfbbe27cced0e45 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 8 Sep 2020 17:53:32 +0200 Subject: [PATCH 771/947] fix tile order for Draft Tile Assembler --- .../maya/publish/submit_maya_deadline.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 747d2727b7..e4048592a7 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -25,6 +25,7 @@ import re import hashlib from datetime import datetime import itertools +from collections import OrderedDict import clique import requests @@ -67,7 +68,7 @@ payload_skeleton = { def _format_tiles( filename, index, tiles_x, tiles_y, - width, height, prefix, origin="blc"): + width, height, prefix): """Generate tile entries for Deadline tile job. Returns two dictionaries - one that can be directly used in Deadline @@ -113,12 +114,14 @@ def _format_tiles( """ tile = 0 out = {"JobInfo": {}, "PluginInfo": {}} - cfg = {} + cfg = OrderedDict() w_space = width / tiles_x h_space = height / tiles_y + cfg["TilesCropped"] = "False" + for tile_x in range(1, tiles_x + 1): - for tile_y in range(1, tiles_y + 1): + for tile_y in reversed(range(1, tiles_y + 1)): tile_prefix = "_tile_{}x{}_{}x{}_".format( tile_x, tile_y, tiles_x, @@ -143,14 +146,13 @@ def _format_tiles( cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename + cfg["Tile{}FileName".format(tile)] = new_filename cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - if origin == "blc": - cfg["Tile{}Y".format(tile)] = (tile_y - 1) * h_space - else: - cfg["Tile{}Y".format(tile)] = int(height) - ((tile_y - 1) * h_space) # noqa: E501 - cfg["Tile{}Width".format(tile)] = tile_x * w_space - cfg["Tile{}Height".format(tile)] = tile_y * h_space + cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) + + cfg["Tile{}Width".format(tile)] = w_space + cfg["Tile{}Height".format(tile)] = h_space tile += 1 return out, cfg @@ -538,7 +540,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "AuxFiles": [], "JobInfo": { "BatchName": payload["JobInfo"]["BatchName"], - "Frames": 0, + "Frames": 1, "Name": "{} - Tile Assembly Job".format( payload["JobInfo"]["Name"]), "OutputDirectory0": @@ -590,7 +592,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload["JobInfo"]["Name"], frame, instance.data.get("tilesX") * instance.data.get("tilesY") # noqa: E501 - ) + ) self.log.info( "... preparing job {}".format( new_payload["JobInfo"]["Name"])) From 504ae4a1b3f054c1fb8327be89675eede5c56978 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 18:22:13 +0200 Subject: [PATCH 772/947] moved setting attributes on apply overrides --- .../config_setting/widgets/anatomy_inputs.py | 4 ++-- .../config_setting/config_setting/widgets/inputs.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index b0aed18e01..6d4417a6aa 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -306,15 +306,15 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot: self._is_overriden = parent_values is not NOT_SET + self._was_overriden = bool(self._is_overriden) self.singleroot_widget.apply_overrides(NOT_SET) self.multiroot_widget.apply_overrides(parent_values) else: self._is_overriden = value is not NOT_SET + self._was_overriden = bool(self._is_overriden) self.singleroot_widget.apply_overrides(value) self.multiroot_widget.apply_overrides(NOT_SET) - self._was_overriden = bool(self._is_overriden) - def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() self.multiroot_widget.hierarchical_style_update() diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 155bcbb74d..b094e4cf67 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -16,6 +16,7 @@ class ConfigObject(AbstractConfigObject): default_state = "" + _as_widget = False _is_overriden = False _is_modified = False _was_overriden = False @@ -44,6 +45,8 @@ class ConfigObject(AbstractConfigObject): @property def was_overriden(self): """Initial state after applying overrides.""" + if self._as_widget: + return self._parent.was_overriden return self._was_overriden @property @@ -673,7 +676,7 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): if self._is_invalid: self._is_modified = True - elif self._is_overriden: + elif self.is_overriden: self._is_modified = self.item_value() != self.override_value else: self._is_modified = self.item_value() != self.global_value @@ -1126,6 +1129,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.updateGeometry() def apply_overrides(self, parent_values): + self._is_modified = False if parent_values is NOT_SET or self.key not in parent_values: override_value = NOT_SET else: @@ -2142,7 +2146,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.global_value != self.start_value def apply_overrides(self, parent_values): - # Make sure this is set to False + self._is_modified = False self._state = None self._child_state = None override_values = NOT_SET @@ -2152,6 +2156,8 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): override_values = parent_values.get(self.key, override_values) self._is_overriden = override_values is not NOT_SET + self._was_overriden = bool(self._is_overriden) + if not self.multiplatform: self.input_fields[0].apply_overrides(parent_values) else: From d22371d0f53436385af97bb63892eb7c307030ab Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 18:22:36 +0200 Subject: [PATCH 773/947] modifiable dict sets overrides instead of setting values --- .../config_setting/widgets/inputs.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b094e4cf67..f975567a6a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1243,11 +1243,16 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.update_style() self.value_changed.emit(self) - def set_values(self, key, value): + def update_global_values(self, key, value): self.origin_key = key self.key_input.setText(key) self.value_input.update_global_values(value) + def apply_overrides(self, key, value): + self.origin_key = key + self.key_input.setText(key) + self.value_input.apply_overrides(value) + @property def is_group(self): return self._parent.is_group @@ -1521,7 +1526,10 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: - item_widget.set_values(key, value) + if self._is_overriden: + item_widget.apply_overrides(key, value) + else: + item_widget.update_global_values(key, value) self.hierarchical_style_update() else: self._on_value_change() @@ -2161,8 +2169,8 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): if not self.multiplatform: self.input_fields[0].apply_overrides(parent_values) else: - for item in self.input_fields: - item.apply_overrides(override_values) + for input_field in self.input_fields: + input_field.apply_overrides(override_values) if not self._is_overriden: self._is_overriden = ( From b03687ea055ff30b39b7c9003c888f28acf0c449 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 8 Sep 2020 18:38:52 +0200 Subject: [PATCH 774/947] anatomy widgets propagate child modifications down the hierarchy --- .../config_setting/widgets/anatomy_inputs.py | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 6d4417a6aa..e59de3980f 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -70,6 +70,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): body_widget.set_content_widget(content_widget) + self.body_widget = body_widget self.label_widget = body_widget.label_widget self.root_widget.value_changed.connect(self._on_value_change) @@ -122,8 +123,10 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): child_state = "child-{}".format(child_state) if child_state != self._child_state: - self.setProperty("state", child_state) - self.style().polish(self) + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) self._child_state = child_state def hierarchical_style_update(self): @@ -239,6 +242,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(body_widget) + self.body_widget = body_widget self.multiroot_label = multiroot_label self.multiroot_checkbox = multiroot_checkbox self.singleroot_widget = singleroot_widget @@ -342,8 +346,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: child_state = "" - self.setProperty("state", child_state) - self.style().polish(self) + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) self.label_widget.setProperty("state", state) self.label_widget.style().polish(self.label_widget) @@ -455,6 +461,8 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): self._parent = parent + self._state = None + self._is_group = True self.any_parent_is_group = False self.key = "templates" @@ -467,6 +475,7 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): template_input_data = { "key": self.key } + self.body_widget = body_widget self.label_widget = body_widget.label_widget self.value_input = RawJsonWidget( template_input_data, self, @@ -481,13 +490,38 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): layout.addWidget(body_widget) def update_global_values(self, values): + self._state = None self.value_input.update_global_values(values) def apply_overrides(self, parent_values): + self._state = None self.value_input.apply_overrides(parent_values) def hierarchical_style_update(self): self.value_input.hierarchical_style_update() + self.update_style() + + def update_style(self): + state = self.style_state( + self.child_invalid, self.child_overriden, self.child_modified + ) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + print(child_state) + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state @property def is_modified(self): From ace606efe6e4bac12e0a2c25060ae6f781335ec3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 09:51:25 +0200 Subject: [PATCH 775/947] renamed studio to system window --- pype/tools/config_setting/config_setting/widgets/base.py | 4 ++-- pype/tools/config_setting/config_setting/widgets/window.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 066c00c96d..d39b5789d9 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -7,14 +7,14 @@ from . import lib from avalon import io -class StudioWidget(QtWidgets.QWidget): +class SystemWidget(QtWidgets.QWidget): is_overidable = False _is_overriden = False _is_group = False _any_parent_is_group = False def __init__(self, parent=None): - super(StudioWidget, self).__init__(parent) + super(SystemWidget, self).__init__(parent) self._ignore_value_changes = False diff --git a/pype/tools/config_setting/config_setting/widgets/window.py b/pype/tools/config_setting/config_setting/widgets/window.py index af23e68f77..7ad46b1a06 100644 --- a/pype/tools/config_setting/config_setting/widgets/window.py +++ b/pype/tools/config_setting/config_setting/widgets/window.py @@ -1,5 +1,5 @@ from Qt import QtWidgets -from .base import StudioWidget, ProjectWidget +from .base import SystemWidget, ProjectWidget class MainWidget(QtWidgets.QWidget): @@ -13,9 +13,9 @@ class MainWidget(QtWidgets.QWidget): header_tab_widget = QtWidgets.QTabWidget(parent=self) - studio_widget = StudioWidget() + studio_widget = SystemWidget() project_widget = ProjectWidget() - header_tab_widget.addTab(studio_widget, "Studio") + header_tab_widget.addTab(studio_widget, "System") header_tab_widget.addTab(project_widget, "Project") layout = QtWidgets.QVBoxLayout(self) From 28931dba6b17aff9a634e06e6b351b9b7b6c9781 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:00:42 +0200 Subject: [PATCH 776/947] removed configs from config tool --- .../project_presets/ftrack/ftrack_config.json | 11 - .../project_presets/global/creator.json | 8 - .../global/slates/example_HD.json | 212 ------------------ .../config/project_presets/maya/capture.json | 108 --------- .../project_presets/plugins/config.json | 1 - .../plugins/ftrack/publish.json | 6 - .../plugins/global/create.json | 1 - .../plugins/global/filter.json | 1 - .../project_presets/plugins/global/load.json | 1 - .../plugins/global/publish.json | 73 ------ .../project_presets/plugins/maya/create.json | 1 - .../project_presets/plugins/maya/filter.json | 9 - .../project_presets/plugins/maya/load.json | 18 -- .../project_presets/plugins/maya/publish.json | 17 -- .../plugins/maya/workfile_build.json | 54 ----- .../project_presets/plugins/nuke/create.json | 8 - .../project_presets/plugins/nuke/load.json | 1 - .../project_presets/plugins/nuke/publish.json | 48 ---- .../plugins/nuke/workfile_build.json | 11 - .../plugins/nukestudio/filter.json | 10 - .../plugins/nukestudio/publish.json | 8 - .../plugins/standalonepublisher/publish.json | 17 -- .../project_presets/plugins/test/create.json | 8 - .../project_presets/plugins/test/publish.json | 10 - .../premiere/asset_default.json | 5 - .../project_presets/premiere/rules_tasks.json | 21 -- .../project_presets/unreal/project_setup.json | 4 - 27 files changed, 672 deletions(-) delete mode 100644 pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json delete mode 100644 pype/tools/config_setting/config/project_presets/global/creator.json delete mode 100644 pype/tools/config_setting/config/project_presets/global/slates/example_HD.json delete mode 100644 pype/tools/config_setting/config/project_presets/maya/capture.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/config.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/create.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/filter.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/load.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/create.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/filter.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/load.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/create.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/load.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/test/create.json delete mode 100644 pype/tools/config_setting/config/project_presets/plugins/test/publish.json delete mode 100644 pype/tools/config_setting/config/project_presets/premiere/asset_default.json delete mode 100644 pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json delete mode 100644 pype/tools/config_setting/config/project_presets/unreal/project_setup.json diff --git a/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json b/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json deleted file mode 100644 index c9dbde4596..0000000000 --- a/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "status_update": { - "_ignore_": ["in progress", "ommited", "on hold"], - "Ready": ["not ready"], - "In Progress" : ["_any_"] - }, - "status_version_to_task": { - "in progress": "in progress", - "approved": "approved" - } -} diff --git a/pype/tools/config_setting/config/project_presets/global/creator.json b/pype/tools/config_setting/config/project_presets/global/creator.json deleted file mode 100644 index d14e779f01..0000000000 --- a/pype/tools/config_setting/config/project_presets/global/creator.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Model": ["model"], - "Render Globals": ["light", "render"], - "Layout": ["layout"], - "Set Dress": ["setdress"], - "Look": ["look"], - "Rig": ["rigging"] -} diff --git a/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json b/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json deleted file mode 100644 index b06391fb63..0000000000 --- a/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "width": 1920, - "height": 1080, - "destination_path": "{destination_path}", - "style": { - "*": { - "font-family": "arial", - "font-color": "#ffffff", - "font-bold": false, - "font-italic": false, - "bg-color": "#0077ff", - "alignment-horizontal": "left", - "alignment-vertical": "top" - }, - "layer": { - "padding": 0, - "margin": 0 - }, - "rectangle": { - "padding": 0, - "margin": 0, - "bg-color": "#E9324B", - "fill": true - }, - "main_frame": { - "padding": 0, - "margin": 0, - "bg-color": "#252525" - }, - "table": { - "padding": 0, - "margin": 0, - "bg-color": "transparent" - }, - "table-item": { - "padding": 5, - "padding-bottom": 10, - "margin": 0, - "bg-color": "#212121", - "bg-alter-color": "#272727", - "font-color": "#dcdcdc", - "font-bold": false, - "font-italic": false, - "alignment-horizontal": "left", - "alignment-vertical": "top", - "word-wrap": false, - "ellide": true, - "max-lines": 1 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "font-bold": true, - "ellide": false, - "word-wrap": true, - "max-lines": null - }, - "table-item-col[1]": { - "font-size": 40, - "padding-left": 10 - }, - "#colorbar": { - "bg-color": "#9932CC" - } - }, - "items": [{ - "type": "layer", - "direction": 1, - "name": "MainLayer", - "style": { - "#MainLayer": { - "width": 1094, - "height": 1000, - "margin": 25, - "padding": 0 - }, - "#LeftSide": { - "margin-right": 25 - } - }, - "items": [{ - "type": "layer", - "name": "LeftSide", - "items": [{ - "type": "layer", - "direction": 1, - "style": { - "table-item": { - "bg-color": "transparent", - "padding-bottom": 20 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "alignment-horizontal": "right" - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "font-bold": true, - "font-size": 40 - } - }, - "items": [{ - "type": "table", - "values": [ - ["Show:", "{project[name]}"] - ], - "style": { - "table-item-field[0:0]": { - "width": 150 - }, - "table-item-field[0:1]": { - "width": 580 - } - } - }, { - "type": "table", - "values": [ - ["Submitting For:", "{intent}"] - ], - "style": { - "table-item-field[0:0]": { - "width": 160 - }, - "table-item-field[0:1]": { - "width": 218, - "alignment-horizontal": "right" - } - } - }] - }, { - "type": "rectangle", - "style": { - "bg-color": "#bc1015", - "width": 1108, - "height": 5, - "fill": true - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Version name:", "{version_name}"], - ["Date:", "{date}"], - ["Shot Types:", "{shot_type}"], - ["Submission Note:", "{submission_note}"] - ], - "style": { - "table-item": { - "padding-bottom": 20 - }, - "table-item-field[0:1]": { - "font-bold": true - }, - "table-item-field[3:0]": { - "word-wrap": true, - "ellide": true, - "max-lines": 4 - }, - "table-item-col[0]": { - "alignment-horizontal": "right", - "width": 150 - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "width": 958 - } - } - }] - }, { - "type": "layer", - "name": "RightSide", - "items": [{ - "type": "placeholder", - "name": "thumbnail", - "path": "{thumbnail_path}", - "style": { - "width": 730, - "height": 412 - } - }, { - "type": "placeholder", - "name": "colorbar", - "path": "{color_bar_path}", - "return_data": true, - "style": { - "width": 730, - "height": 55 - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Vendor:", "{vendor}"], - ["Shot Name:", "{shot_name}"], - ["Frames:", "{frame_start} - {frame_end} ({duration})"] - ], - "style": { - "table-item-col[0]": { - "alignment-horizontal": "left", - "width": 200 - }, - "table-item-col[1]": { - "alignment-horizontal": "right", - "width": 530, - "font-size": 30 - } - } - }] - }] - }] -} diff --git a/pype/tools/config_setting/config/project_presets/maya/capture.json b/pype/tools/config_setting/config/project_presets/maya/capture.json deleted file mode 100644 index b6c4893034..0000000000 --- a/pype/tools/config_setting/config/project_presets/maya/capture.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "Codec": { - "compression": "jpg", - "format": "image", - "quality": 95 - }, - "Display Options": { - "background": [ - 0.7137254901960784, - 0.7137254901960784, - 0.7137254901960784 - ], - "backgroundBottom": [ - 0.7137254901960784, - 0.7137254901960784, - 0.7137254901960784 - ], - "backgroundTop": [ - 0.7137254901960784, - 0.7137254901960784, - 0.7137254901960784 - ], - "override_display": true - }, - "Generic": { - "isolate_view": true, - "off_screen": true - }, - "IO": { - "name": "", - "open_finished": false, - "raw_frame_numbers": false, - "recent_playblasts": [], - "save_file": false - }, - "PanZoom": { - "pan_zoom": true - }, - "Renderer": { - "rendererName": "vp2Renderer" - }, - "Resolution": { - "height": 1080, - "mode": "Custom", - "percent": 1.0, - "width": 1920 - }, - "Time Range": { - "end_frame": 25, - "frame": "", - "start_frame": 0, - "time": "Time Slider" - }, - "Viewport Options": { - "cameras": false, - "clipGhosts": false, - "controlVertices": false, - "deformers": false, - "dimensions": false, - "displayLights": 0, - "dynamicConstraints": false, - "dynamics": false, - "fluids": false, - "follicles": false, - "gpuCacheDisplayFilter": false, - "greasePencils": false, - "grid": false, - "hairSystems": false, - "handles": false, - "high_quality": true, - "hud": false, - "hulls": false, - "ikHandles": false, - "imagePlane": false, - "joints": false, - "lights": false, - "locators": false, - "manipulators": false, - "motionTrails": false, - "nCloths": false, - "nParticles": false, - "nRigids": false, - "nurbsCurves": false, - "nurbsSurfaces": false, - "override_viewport_options": true, - "particleInstancers": false, - "pivots": false, - "planes": false, - "pluginShapes": false, - "polymeshes": true, - "shadows": false, - "strokes": false, - "subdivSurfaces": false, - "textures": false, - "twoSidedLighting": true - }, - "Camera Options": { - "displayGateMask": false, - "displayResolution": false, - "displayFilmGate": false, - "displayFieldChart": false, - "displaySafeAction": false, - "displaySafeTitle": false, - "displayFilmPivot": false, - "displayFilmOrigin": false, - "overscan": 1.0 - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/config.json b/pype/tools/config_setting/config/project_presets/plugins/config.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json b/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json deleted file mode 100644 index d0469ae4f7..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "IntegrateFtrackNote": { - "note_with_intent_template": "{intent}: {comment}", - "note_labels": [] - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/create.json b/pype/tools/config_setting/config/project_presets/plugins/global/create.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/global/create.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/filter.json b/pype/tools/config_setting/config/project_presets/plugins/global/filter.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/global/filter.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/load.json b/pype/tools/config_setting/config/project_presets/plugins/global/load.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/global/load.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/publish.json b/pype/tools/config_setting/config/project_presets/plugins/global/publish.json deleted file mode 100644 index 6e51e00497..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/global/publish.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "IntegrateMasterVersion": { - "enabled": false - }, - "ExtractReview": { - "__documentation__": "http://pype.club/docs/admin_presets_plugins", - "profiles": [ - { - "families": [], - "hosts": [], - "outputs": { - "h264": { - "filter": { - "families": ["render", "review", "ftrack"] - }, - "ext": "mp4", - "ffmpeg_args": { - "input": [ - "-gamma 2.2" - ], - "video_filters": [], - "audio_filters": [], - "output": [ - "-pix_fmt yuv420p", - "-crf 18", - "-intra" - ] - }, - "tags": ["burnin", "ftrackreview"] - } - } - } - ] - }, - "ExtractBurnin": { - "options": { - "opacity": 1, - "x_offset": 5, - "y_offset": 5, - "bg_padding": 5, - "bg_opacity": 0.5, - "font_size": 42 - }, - "fields": { - - }, - "profiles": [ - { - "burnins": { - "burnin": { - "TOP_LEFT": "{yy}-{mm}-{dd}", - "TOP_RIGHT": "{anatomy[version]}", - "TOP_CENTERED": "", - "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", - "BOTTOM_CENTERED": "{asset}", - "BOTTOM_LEFT": "{username}" - } - } - } - ] - }, - "IntegrateAssetNew": { - "template_name_profiles": { - "publish": { - "families": [], - "tasks": [] - }, - "render": { - "families": ["review", "render", "prerender"] - } - } - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/create.json b/pype/tools/config_setting/config/project_presets/plugins/maya/create.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/maya/create.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json b/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json deleted file mode 100644 index 83d6f05f31..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Preset n1": { - "ValidateNoAnimation": false, - "ValidateShapeDefaultNames": false - }, - "Preset n2": { - "ValidateNoAnimation": false - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/load.json b/pype/tools/config_setting/config/project_presets/plugins/maya/load.json deleted file mode 100644 index 260fbb35ee..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/maya/load.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "colors": { - "model": [0.821, 0.518, 0.117], - "rig": [0.144, 0.443, 0.463], - "pointcache": [0.368, 0.821, 0.117], - "animation": [0.368, 0.821, 0.117], - "ass": [1.0, 0.332, 0.312], - "camera": [0.447, 0.312, 1.0], - "fbx": [1.0, 0.931, 0.312], - "mayaAscii": [0.312, 1.0, 0.747], - "setdress": [0.312, 1.0, 0.747], - "layout": [0.312, 1.0, 0.747], - "vdbcache": [0.312, 1.0, 0.428], - "vrayproxy": [0.258, 0.95, 0.541], - "yeticache": [0.2, 0.8, 0.3], - "yetiRig": [0, 0.8, 0.5] - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json b/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json deleted file mode 100644 index 2e2b3164f3..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ValidateModelName": { - "enabled": false, - "material_file": "/path/to/shader_name_definition.txt", - "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" - }, - "ValidateAssemblyName": { - "enabled": false - }, - "ValidateShaderName": { - "enabled": false, - "regex": "(?P.*)_(.*)_SHD" - }, - "ValidateMeshHasOverlappingUVs": { - "enabled": false - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json b/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json deleted file mode 100644 index 2872b783cb..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json +++ /dev/null @@ -1,54 +0,0 @@ -[{ - "tasks": ["lighting"], - - "current_context": [{ - "subset_name_filters": [".+[Mm]ain"], - "families": ["model"], - "repre_names": ["abc", "ma"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["animation", "pointcache"], - "repre_names": ["abc"], - "loaders": ["ReferenceLoader"] - },{ - "families": ["rendersetup"], - "repre_names": ["json"], - "loaders": ["RenderSetupLoader"] - }, { - "families": ["camera"], - "repre_names": ["abc"], - "loaders": ["ReferenceLoader"] - }], - - "linked_assets": [{ - "families": ["setdress"], - "repre_names": ["ma"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["ass"], - "repre_names": ["ass"], - "loaders":["assLoader"] - }] -}, { - "tasks": ["animation"], - - "current_context": [{ - "families": ["camera"], - "repre_names": ["abc", "ma"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["audio"], - "repre_names": ["wav"], - "loaders": ["RenderSetupLoader"] - }], - - "linked_assets": [{ - "families": ["setdress"], - "repre_names": ["proxy"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["rig"], - "repre_names": ["ass"], - "loaders": ["rigLoader"] - }] -}] diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json deleted file mode 100644 index 4deb0b4ad5..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "CreateWriteRender": { - "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" - }, - "CreateWritePrerender": { - "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json deleted file mode 100644 index ab0d0e76a5..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "ExtractThumbnail": { - "nodes": { - "Reformat": [ - ["type", "to format"], - ["format", "HD_1080"], - ["filter", "Lanczos6"], - ["black_outside", true], - ["pbb", false] - ] - } - }, - "ValidateNukeWriteKnobs": { - "enabled": false, - "knobs": { - "render": { - "review": true - } - } - }, - "ExtractReviewDataLut": { - "__documentation__": { - "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", - "enabled": "keep in on `false` if you want Nuke baking colorspace workflow applied else FFmpeg will convert imgsequence with baked LUT" - }, - "enabled": false - }, - "ExtractReviewDataMov": { - "__documentation__": { - "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", - "enabled": "keep in on `true` if you want Nuke baking colorspace workflow applied" - }, - "enabled": true, - "viewer_lut_raw": false - }, - "ExtractSlateFrame": { - "__documentation__": { - "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`" - }, - "viewer_lut_raw": false - }, - "NukeSubmitDeadline": { - "deadline_priority": 50, - "deadline_pool": "", - "deadline_pool_secondary": "", - "deadline_chunk_size": 1 - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json deleted file mode 100644 index d3613c929e..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json +++ /dev/null @@ -1,11 +0,0 @@ -[{ - "tasks": ["compositing"], - - "current_context": [{ - "families": ["render", "plate"], - "repre_names": ["exr" ,"dpx"], - "loaders": ["LoadSequence"] - }], - - "linked_assets": [] -}] diff --git a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json deleted file mode 100644 index bd6a0dc1bd..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strict": { - "ValidateVersion": true, - "VersionUpWorkfile": true - }, - "benevolent": { - "ValidateVersion": false, - "VersionUpWorkfile": false - } -} \ No newline at end of file diff --git a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json deleted file mode 100644 index 8c4ad133f1..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "CollectInstanceVersion": { - "enabled": false - }, - "ExtractReviewCutUpVideo": { - "tags_addition": [] - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json b/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json deleted file mode 100644 index ecfff12db9..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ExtractReviewSP": { - "outputs": { - "h264": { - "input": [ - "-gamma 2.2" - ], - "output": [ - "-pix_fmt yuv420p", - "-crf 18" - ], - "tags": ["preview"], - "ext": "mov" - } - } - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/test/create.json b/pype/tools/config_setting/config/project_presets/plugins/test/create.json deleted file mode 100644 index fa0b2fc05f..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/test/create.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "MyTestCreator": { - "my_test_property": "B", - "active": false, - "new_property": "new", - "family": "new_family" - } -} diff --git a/pype/tools/config_setting/config/project_presets/plugins/test/publish.json b/pype/tools/config_setting/config/project_presets/plugins/test/publish.json deleted file mode 100644 index 3180dd5d8a..0000000000 --- a/pype/tools/config_setting/config/project_presets/plugins/test/publish.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "MyTestPlugin": { - "label": "loaded from preset", - "optional": true, - "families": ["changed", "by", "preset"] - }, - "MyTestRemovedPlugin": { - "enabled": false - } -} diff --git a/pype/tools/config_setting/config/project_presets/premiere/asset_default.json b/pype/tools/config_setting/config/project_presets/premiere/asset_default.json deleted file mode 100644 index 84d2bde3d8..0000000000 --- a/pype/tools/config_setting/config/project_presets/premiere/asset_default.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "frameStart": 1001, - "handleStart": 0, - "handleEnd": 0 -} diff --git a/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json b/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json deleted file mode 100644 index 333c9cd70b..0000000000 --- a/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "defaultTasks": ["Layout", "Animation"], - "taskToSubsets": { - "Layout": ["reference", "audio"], - "Animation": ["audio"] - }, - "subsetToRepresentations": { - "reference": { - "preset": "h264", - "representation": "mp4" - }, - "thumbnail": { - "preset": "jpeg_thumb", - "representation": "jpg" - }, - "audio": { - "preset": "48khz", - "representation": "wav" - } - } -} diff --git a/pype/tools/config_setting/config/project_presets/unreal/project_setup.json b/pype/tools/config_setting/config/project_presets/unreal/project_setup.json deleted file mode 100644 index 8a4dffc526..0000000000 --- a/pype/tools/config_setting/config/project_presets/unreal/project_setup.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "dev_mode": false, - "install_unreal_python_engine": false -} From f2bb1af12836a9a298212525da6b9ea0a02e1215 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:03:35 +0200 Subject: [PATCH 777/947] moved config to folder configurations --- pype/api.py | 2 +- pype/{ => configurations}/config.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pype/{ => configurations}/config.py (100%) diff --git a/pype/api.py b/pype/api.py index e2705f81ea..88dfab19ec 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,4 +1,4 @@ -from . import config +from .configurations import config from pypeapp import ( Logger, Anatomy, diff --git a/pype/config.py b/pype/configurations/config.py similarity index 100% rename from pype/config.py rename to pype/configurations/config.py From d5e7ccce3f9dbf3b9c5b76180d4cd256e8c0c44d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:04:37 +0200 Subject: [PATCH 778/947] copied pype-config to pype --- .../configurations/defaults/anatomy/README.md | 0 .../defaults/anatomy/default.yaml | 29 ++ .../defaults/anatomy/roots.json | 5 + .../defaults/environments/avalon.json | 16 ++ .../defaults/environments/blender.json | 7 + .../defaults/environments/celaction.json | 3 + .../defaults/environments/deadline.json | 3 + .../defaults/environments/ftrack.json | 18 ++ .../defaults/environments/global.json | 44 +++ .../defaults/environments/harmony.json | 4 + .../defaults/environments/houdini.json | 12 + .../defaults/environments/maya.json | 14 + .../defaults/environments/maya_2018.json | 11 + .../defaults/environments/maya_2020.json | 11 + .../defaults/environments/mayabatch.json | 14 + .../defaults/environments/mayabatch_2019.json | 11 + .../defaults/environments/mtoa_3.1.1.json | 23 ++ .../defaults/environments/muster.json | 3 + .../defaults/environments/nuke.json | 15 + .../defaults/environments/nukestudio.json | 11 + .../environments/nukestudio_10.0.json | 4 + .../defaults/environments/nukex.json | 10 + .../defaults/environments/nukex_10.0.json | 4 + .../defaults/environments/photoshop.json | 4 + .../defaults/environments/premiere.json | 11 + .../defaults/environments/resolve.json | 40 +++ .../defaults/environments/storyboardpro.json | 4 + .../defaults/environments/unreal_4.24.json | 5 + .../defaults/environments/vray_4300.json | 15 + .../defaults/launchers/blender_2.80.toml | 7 + .../defaults/launchers/blender_2.81.toml | 7 + .../defaults/launchers/blender_2.82.toml | 7 + .../defaults/launchers/blender_2.83.toml | 7 + .../defaults/launchers/celaction_local.toml | 8 + .../defaults/launchers/celaction_publish.toml | 7 + .../defaults/launchers/darwin/blender_2.82 | 2 + .../defaults/launchers/darwin/harmony_17 | 9 + .../launchers/darwin/harmony_17_launch | 5 + .../defaults/launchers/darwin/python3 | 2 + .../defaults/launchers/harmony_17.toml | 8 + .../defaults/launchers/houdini_16.toml | 7 + .../defaults/launchers/houdini_17.toml | 7 + .../defaults/launchers/houdini_18.toml | 7 + .../defaults/launchers/linux/maya2016 | 8 + .../defaults/launchers/linux/maya2017 | 8 + .../defaults/launchers/linux/maya2018 | 8 + .../defaults/launchers/linux/maya2019 | 8 + .../defaults/launchers/linux/maya2020 | 8 + .../defaults/launchers/linux/nuke11.3 | 2 + .../defaults/launchers/linux/nuke12.0 | 2 + .../defaults/launchers/linux/nukestudio11.3 | 2 + .../defaults/launchers/linux/nukestudio12.0 | 2 + .../defaults/launchers/linux/nukex11.3 | 2 + .../defaults/launchers/linux/nukex12.0 | 2 + .../defaults/launchers/maya_2016.toml | 26 ++ .../defaults/launchers/maya_2017.toml | 28 ++ .../defaults/launchers/maya_2018.toml | 14 + .../defaults/launchers/maya_2019.toml | 14 + .../defaults/launchers/maya_2020.toml | 14 + .../defaults/launchers/mayabatch_2019.toml | 17 ++ .../defaults/launchers/mayabatch_2020.toml | 17 ++ .../defaults/launchers/mayapy2016.toml | 17 ++ .../defaults/launchers/mayapy2017.toml | 17 ++ .../defaults/launchers/mayapy2018.toml | 17 ++ .../defaults/launchers/mayapy2019.toml | 17 ++ .../defaults/launchers/mayapy2020.toml | 17 ++ .../defaults/launchers/myapp.toml | 5 + .../defaults/launchers/nuke_10.0.toml | 7 + .../defaults/launchers/nuke_11.0.toml | 7 + .../defaults/launchers/nuke_11.2.toml | 7 + .../defaults/launchers/nuke_11.3.toml | 7 + .../defaults/launchers/nuke_12.0.toml | 7 + .../defaults/launchers/nukestudio_10.0.toml | 7 + .../defaults/launchers/nukestudio_11.0.toml | 7 + .../defaults/launchers/nukestudio_11.2.toml | 7 + .../defaults/launchers/nukestudio_11.3.toml | 7 + .../defaults/launchers/nukestudio_12.0.toml | 7 + .../defaults/launchers/nukex_10.0.toml | 7 + .../defaults/launchers/nukex_11.0.toml | 7 + .../defaults/launchers/nukex_11.2.toml | 7 + .../defaults/launchers/nukex_11.3.toml | 7 + .../defaults/launchers/nukex_12.0.toml | 7 + .../defaults/launchers/photoshop_2020.toml | 8 + .../defaults/launchers/premiere_2019.toml | 8 + .../defaults/launchers/premiere_2020.toml | 9 + .../defaults/launchers/python_2.toml | 10 + .../defaults/launchers/python_3.toml | 10 + .../defaults/launchers/resolve_16.toml | 9 + .../defaults/launchers/shell.toml | 7 + .../defaults/launchers/storyboardpro_7.toml | 8 + .../defaults/launchers/unreal_4.24.toml | 8 + .../launchers/windows/blender_2.80.bat | 11 + .../launchers/windows/blender_2.81.bat | 11 + .../launchers/windows/blender_2.82.bat | 11 + .../launchers/windows/blender_2.83.bat | 11 + .../launchers/windows/celaction_local.bat | 19 ++ .../launchers/windows/celaction_publish.bat | 3 + .../defaults/launchers/windows/harmony_17.bat | 13 + .../defaults/launchers/windows/houdini_16.bat | 13 + .../defaults/launchers/windows/houdini_17.bat | 13 + .../defaults/launchers/windows/houdini_18.bat | 13 + .../defaults/launchers/windows/maya2016.bat | 17 ++ .../defaults/launchers/windows/maya2017.bat | 17 ++ .../defaults/launchers/windows/maya2018.bat | 17 ++ .../defaults/launchers/windows/maya2019.bat | 17 ++ .../defaults/launchers/windows/maya2020.bat | 17 ++ .../launchers/windows/mayabatch2019.bat | 14 + .../launchers/windows/mayabatch2020.bat | 14 + .../defaults/launchers/windows/mayapy2016.bat | 13 + .../defaults/launchers/windows/mayapy2017.bat | 13 + .../defaults/launchers/windows/mayapy2018.bat | 13 + .../defaults/launchers/windows/mayapy2019.bat | 13 + .../defaults/launchers/windows/mayapy2020.bat | 13 + .../defaults/launchers/windows/nuke10.0.bat | 13 + .../defaults/launchers/windows/nuke11.0.bat | 13 + .../defaults/launchers/windows/nuke11.2.bat | 13 + .../defaults/launchers/windows/nuke11.3.bat | 13 + .../defaults/launchers/windows/nuke12.0.bat | 13 + .../launchers/windows/nukestudio10.0.bat | 13 + .../launchers/windows/nukestudio11.0.bat | 13 + .../launchers/windows/nukestudio11.2.bat | 13 + .../launchers/windows/nukestudio11.3.bat | 13 + .../launchers/windows/nukestudio12.0.bat | 13 + .../defaults/launchers/windows/nukex10.0.bat | 13 + .../defaults/launchers/windows/nukex11.0.bat | 13 + .../defaults/launchers/windows/nukex11.2.bat | 13 + .../defaults/launchers/windows/nukex11.3.bat | 13 + .../defaults/launchers/windows/nukex12.0.bat | 13 + .../launchers/windows/photoshop_2020.bat | 15 + .../launchers/windows/premiere_pro_2019.bat | 14 + .../launchers/windows/premiere_pro_2020.bat | 13 + .../defaults/launchers/windows/python3.bat | 13 + .../defaults/launchers/windows/resolve_16.bat | 17 ++ .../defaults/launchers/windows/shell.bat | 2 + .../launchers/windows/storyboardpro_7.bat | 13 + .../defaults/launchers/windows/unreal.bat | 11 + .../presets/colorspace/aces103-cg.json | 46 +++ .../defaults/presets/colorspace/default.json | 42 +++ .../defaults/presets/dataflow/aces-exr.json | 58 ++++ .../defaults/presets/dataflow/default.json | 55 ++++ .../presets/ftrack/ftrack_config.json | 16 ++ .../ftrack/ftrack_custom_attributes.json | 165 +++++++++++ .../ftrack/partnership_ftrack_cred.json | 5 + .../presets/ftrack/plugins/server.json | 1 + .../defaults/presets/ftrack/plugins/user.json | 5 + .../presets/ftrack/project_defaults.json | 18 ++ .../configurations/defaults/presets/init.json | 4 + .../defaults/presets/maya/capture.json | 108 ++++++++ .../presets/muster/templates_mapping.json | 19 ++ .../defaults/presets/nukestudio/tags.json | 262 ++++++++++++++++++ .../presets/plugins/celaction/publish.json | 10 + .../defaults/presets/plugins/config.json | 1 + .../presets/plugins/ftrack/publish.json | 6 + .../presets/plugins/global/create.json | 1 + .../presets/plugins/global/filter.json | 1 + .../defaults/presets/plugins/global/load.json | 1 + .../presets/plugins/global/publish.json | 86 ++++++ .../defaults/presets/plugins/maya/create.json | 1 + .../defaults/presets/plugins/maya/filter.json | 9 + .../defaults/presets/plugins/maya/load.json | 18 ++ .../presets/plugins/maya/publish.json | 17 ++ .../presets/plugins/maya/workfile_build.json | 54 ++++ .../defaults/presets/plugins/nuke/create.json | 8 + .../defaults/presets/plugins/nuke/load.json | 1 + .../presets/plugins/nuke/publish.json | 48 ++++ .../presets/plugins/nuke/workfile_build.json | 11 + .../presets/plugins/nukestudio/filter.json | 10 + .../presets/plugins/nukestudio/publish.json | 8 + .../presets/plugins/resolve/create.json | 7 + .../plugins/standalonepublisher/publish.json | 25 ++ .../defaults/presets/plugins/test/create.json | 8 + .../presets/plugins/test/publish.json | 10 + .../presets/premiere/asset_default.json | 5 + .../presets/premiere/rules_tasks.json | 21 ++ .../presets/standalone_publish/families.json | 90 ++++++ .../defaults/presets/tools/creator.json | 8 + .../tools/project_folder_structure.json | 22 ++ .../defaults/presets/tools/pyblish.json | 17 ++ .../presets/tools/slates/example_HD.json | 212 ++++++++++++++ .../defaults/presets/tools/sw_folders.json | 8 + .../defaults/presets/tools/workfiles.json | 7 + .../defaults/presets/tray/menu_items.json | 28 ++ .../presets/unreal/project_setup.json | 4 + .../plugins/celaction/publish.json | 11 + .../plugins/config.json | 1 + .../plugins/ftrack/publish.json | 7 + .../plugins/global/create.json | 1 + .../plugins/global/filter.json | 1 + .../plugins/global/load.json | 1 + .../plugins/global/publish.json | 97 +++++++ .../plugins/maya/create.json | 1 + .../plugins/maya/filter.json | 9 + .../plugins/maya/load.json | 18 ++ .../plugins/maya/publish.json | 17 ++ .../plugins/maya/workfile_build.json | 136 +++++++++ .../plugins/nuke/create.json | 8 + .../plugins/nuke/load.json | 1 + .../plugins/nuke/publish.json | 53 ++++ .../plugins/nuke/workfile_build.json | 23 ++ .../plugins/nukestudio/filter.json | 10 + .../plugins/nukestudio/publish.json | 9 + .../plugins/resolve/create.json | 7 + .../plugins/standalonepublisher/publish.json | 27 ++ .../plugins/test/create.json | 8 + .../plugins/test/publish.json | 10 + .../global/applications.json | 39 +++ .../studio_configurations/global/intent.json | 8 + .../studio_configurations/global/tools.json | 6 + .../global/tray_modules.json | 28 ++ .../muster/templates_mapping.json | 19 ++ .../standalone_publish/families.json | 90 ++++++ 211 files changed, 3697 insertions(+) create mode 100644 pype/configurations/defaults/anatomy/README.md create mode 100644 pype/configurations/defaults/anatomy/default.yaml create mode 100644 pype/configurations/defaults/anatomy/roots.json create mode 100644 pype/configurations/defaults/environments/avalon.json create mode 100644 pype/configurations/defaults/environments/blender.json create mode 100644 pype/configurations/defaults/environments/celaction.json create mode 100644 pype/configurations/defaults/environments/deadline.json create mode 100644 pype/configurations/defaults/environments/ftrack.json create mode 100644 pype/configurations/defaults/environments/global.json create mode 100644 pype/configurations/defaults/environments/harmony.json create mode 100644 pype/configurations/defaults/environments/houdini.json create mode 100644 pype/configurations/defaults/environments/maya.json create mode 100644 pype/configurations/defaults/environments/maya_2018.json create mode 100644 pype/configurations/defaults/environments/maya_2020.json create mode 100644 pype/configurations/defaults/environments/mayabatch.json create mode 100644 pype/configurations/defaults/environments/mayabatch_2019.json create mode 100644 pype/configurations/defaults/environments/mtoa_3.1.1.json create mode 100644 pype/configurations/defaults/environments/muster.json create mode 100644 pype/configurations/defaults/environments/nuke.json create mode 100644 pype/configurations/defaults/environments/nukestudio.json create mode 100644 pype/configurations/defaults/environments/nukestudio_10.0.json create mode 100644 pype/configurations/defaults/environments/nukex.json create mode 100644 pype/configurations/defaults/environments/nukex_10.0.json create mode 100644 pype/configurations/defaults/environments/photoshop.json create mode 100644 pype/configurations/defaults/environments/premiere.json create mode 100644 pype/configurations/defaults/environments/resolve.json create mode 100644 pype/configurations/defaults/environments/storyboardpro.json create mode 100644 pype/configurations/defaults/environments/unreal_4.24.json create mode 100644 pype/configurations/defaults/environments/vray_4300.json create mode 100644 pype/configurations/defaults/launchers/blender_2.80.toml create mode 100644 pype/configurations/defaults/launchers/blender_2.81.toml create mode 100644 pype/configurations/defaults/launchers/blender_2.82.toml create mode 100644 pype/configurations/defaults/launchers/blender_2.83.toml create mode 100644 pype/configurations/defaults/launchers/celaction_local.toml create mode 100644 pype/configurations/defaults/launchers/celaction_publish.toml create mode 100644 pype/configurations/defaults/launchers/darwin/blender_2.82 create mode 100644 pype/configurations/defaults/launchers/darwin/harmony_17 create mode 100644 pype/configurations/defaults/launchers/darwin/harmony_17_launch create mode 100644 pype/configurations/defaults/launchers/darwin/python3 create mode 100644 pype/configurations/defaults/launchers/harmony_17.toml create mode 100644 pype/configurations/defaults/launchers/houdini_16.toml create mode 100644 pype/configurations/defaults/launchers/houdini_17.toml create mode 100644 pype/configurations/defaults/launchers/houdini_18.toml create mode 100644 pype/configurations/defaults/launchers/linux/maya2016 create mode 100644 pype/configurations/defaults/launchers/linux/maya2017 create mode 100644 pype/configurations/defaults/launchers/linux/maya2018 create mode 100644 pype/configurations/defaults/launchers/linux/maya2019 create mode 100644 pype/configurations/defaults/launchers/linux/maya2020 create mode 100644 pype/configurations/defaults/launchers/linux/nuke11.3 create mode 100644 pype/configurations/defaults/launchers/linux/nuke12.0 create mode 100644 pype/configurations/defaults/launchers/linux/nukestudio11.3 create mode 100644 pype/configurations/defaults/launchers/linux/nukestudio12.0 create mode 100644 pype/configurations/defaults/launchers/linux/nukex11.3 create mode 100644 pype/configurations/defaults/launchers/linux/nukex12.0 create mode 100644 pype/configurations/defaults/launchers/maya_2016.toml create mode 100644 pype/configurations/defaults/launchers/maya_2017.toml create mode 100644 pype/configurations/defaults/launchers/maya_2018.toml create mode 100644 pype/configurations/defaults/launchers/maya_2019.toml create mode 100644 pype/configurations/defaults/launchers/maya_2020.toml create mode 100644 pype/configurations/defaults/launchers/mayabatch_2019.toml create mode 100644 pype/configurations/defaults/launchers/mayabatch_2020.toml create mode 100644 pype/configurations/defaults/launchers/mayapy2016.toml create mode 100644 pype/configurations/defaults/launchers/mayapy2017.toml create mode 100644 pype/configurations/defaults/launchers/mayapy2018.toml create mode 100644 pype/configurations/defaults/launchers/mayapy2019.toml create mode 100644 pype/configurations/defaults/launchers/mayapy2020.toml create mode 100644 pype/configurations/defaults/launchers/myapp.toml create mode 100644 pype/configurations/defaults/launchers/nuke_10.0.toml create mode 100644 pype/configurations/defaults/launchers/nuke_11.0.toml create mode 100644 pype/configurations/defaults/launchers/nuke_11.2.toml create mode 100644 pype/configurations/defaults/launchers/nuke_11.3.toml create mode 100644 pype/configurations/defaults/launchers/nuke_12.0.toml create mode 100644 pype/configurations/defaults/launchers/nukestudio_10.0.toml create mode 100644 pype/configurations/defaults/launchers/nukestudio_11.0.toml create mode 100644 pype/configurations/defaults/launchers/nukestudio_11.2.toml create mode 100644 pype/configurations/defaults/launchers/nukestudio_11.3.toml create mode 100644 pype/configurations/defaults/launchers/nukestudio_12.0.toml create mode 100644 pype/configurations/defaults/launchers/nukex_10.0.toml create mode 100644 pype/configurations/defaults/launchers/nukex_11.0.toml create mode 100644 pype/configurations/defaults/launchers/nukex_11.2.toml create mode 100644 pype/configurations/defaults/launchers/nukex_11.3.toml create mode 100644 pype/configurations/defaults/launchers/nukex_12.0.toml create mode 100644 pype/configurations/defaults/launchers/photoshop_2020.toml create mode 100644 pype/configurations/defaults/launchers/premiere_2019.toml create mode 100644 pype/configurations/defaults/launchers/premiere_2020.toml create mode 100644 pype/configurations/defaults/launchers/python_2.toml create mode 100644 pype/configurations/defaults/launchers/python_3.toml create mode 100644 pype/configurations/defaults/launchers/resolve_16.toml create mode 100644 pype/configurations/defaults/launchers/shell.toml create mode 100644 pype/configurations/defaults/launchers/storyboardpro_7.toml create mode 100644 pype/configurations/defaults/launchers/unreal_4.24.toml create mode 100644 pype/configurations/defaults/launchers/windows/blender_2.80.bat create mode 100644 pype/configurations/defaults/launchers/windows/blender_2.81.bat create mode 100644 pype/configurations/defaults/launchers/windows/blender_2.82.bat create mode 100644 pype/configurations/defaults/launchers/windows/blender_2.83.bat create mode 100644 pype/configurations/defaults/launchers/windows/celaction_local.bat create mode 100644 pype/configurations/defaults/launchers/windows/celaction_publish.bat create mode 100644 pype/configurations/defaults/launchers/windows/harmony_17.bat create mode 100644 pype/configurations/defaults/launchers/windows/houdini_16.bat create mode 100644 pype/configurations/defaults/launchers/windows/houdini_17.bat create mode 100644 pype/configurations/defaults/launchers/windows/houdini_18.bat create mode 100644 pype/configurations/defaults/launchers/windows/maya2016.bat create mode 100644 pype/configurations/defaults/launchers/windows/maya2017.bat create mode 100644 pype/configurations/defaults/launchers/windows/maya2018.bat create mode 100644 pype/configurations/defaults/launchers/windows/maya2019.bat create mode 100644 pype/configurations/defaults/launchers/windows/maya2020.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayabatch2019.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayabatch2020.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayapy2016.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayapy2017.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayapy2018.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayapy2019.bat create mode 100644 pype/configurations/defaults/launchers/windows/mayapy2020.bat create mode 100644 pype/configurations/defaults/launchers/windows/nuke10.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nuke11.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nuke11.2.bat create mode 100644 pype/configurations/defaults/launchers/windows/nuke11.3.bat create mode 100644 pype/configurations/defaults/launchers/windows/nuke12.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukestudio10.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukestudio11.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukestudio11.2.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukestudio11.3.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukestudio12.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukex10.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukex11.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukex11.2.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukex11.3.bat create mode 100644 pype/configurations/defaults/launchers/windows/nukex12.0.bat create mode 100644 pype/configurations/defaults/launchers/windows/photoshop_2020.bat create mode 100644 pype/configurations/defaults/launchers/windows/premiere_pro_2019.bat create mode 100644 pype/configurations/defaults/launchers/windows/premiere_pro_2020.bat create mode 100644 pype/configurations/defaults/launchers/windows/python3.bat create mode 100644 pype/configurations/defaults/launchers/windows/resolve_16.bat create mode 100644 pype/configurations/defaults/launchers/windows/shell.bat create mode 100644 pype/configurations/defaults/launchers/windows/storyboardpro_7.bat create mode 100644 pype/configurations/defaults/launchers/windows/unreal.bat create mode 100644 pype/configurations/defaults/presets/colorspace/aces103-cg.json create mode 100644 pype/configurations/defaults/presets/colorspace/default.json create mode 100644 pype/configurations/defaults/presets/dataflow/aces-exr.json create mode 100644 pype/configurations/defaults/presets/dataflow/default.json create mode 100644 pype/configurations/defaults/presets/ftrack/ftrack_config.json create mode 100644 pype/configurations/defaults/presets/ftrack/ftrack_custom_attributes.json create mode 100644 pype/configurations/defaults/presets/ftrack/partnership_ftrack_cred.json create mode 100644 pype/configurations/defaults/presets/ftrack/plugins/server.json create mode 100644 pype/configurations/defaults/presets/ftrack/plugins/user.json create mode 100644 pype/configurations/defaults/presets/ftrack/project_defaults.json create mode 100644 pype/configurations/defaults/presets/init.json create mode 100644 pype/configurations/defaults/presets/maya/capture.json create mode 100644 pype/configurations/defaults/presets/muster/templates_mapping.json create mode 100644 pype/configurations/defaults/presets/nukestudio/tags.json create mode 100644 pype/configurations/defaults/presets/plugins/celaction/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/config.json create mode 100644 pype/configurations/defaults/presets/plugins/ftrack/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/global/create.json create mode 100644 pype/configurations/defaults/presets/plugins/global/filter.json create mode 100644 pype/configurations/defaults/presets/plugins/global/load.json create mode 100644 pype/configurations/defaults/presets/plugins/global/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/maya/create.json create mode 100644 pype/configurations/defaults/presets/plugins/maya/filter.json create mode 100644 pype/configurations/defaults/presets/plugins/maya/load.json create mode 100644 pype/configurations/defaults/presets/plugins/maya/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/maya/workfile_build.json create mode 100644 pype/configurations/defaults/presets/plugins/nuke/create.json create mode 100644 pype/configurations/defaults/presets/plugins/nuke/load.json create mode 100644 pype/configurations/defaults/presets/plugins/nuke/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/nuke/workfile_build.json create mode 100644 pype/configurations/defaults/presets/plugins/nukestudio/filter.json create mode 100644 pype/configurations/defaults/presets/plugins/nukestudio/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/resolve/create.json create mode 100644 pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json create mode 100644 pype/configurations/defaults/presets/plugins/test/create.json create mode 100644 pype/configurations/defaults/presets/plugins/test/publish.json create mode 100644 pype/configurations/defaults/presets/premiere/asset_default.json create mode 100644 pype/configurations/defaults/presets/premiere/rules_tasks.json create mode 100644 pype/configurations/defaults/presets/standalone_publish/families.json create mode 100644 pype/configurations/defaults/presets/tools/creator.json create mode 100644 pype/configurations/defaults/presets/tools/project_folder_structure.json create mode 100644 pype/configurations/defaults/presets/tools/pyblish.json create mode 100644 pype/configurations/defaults/presets/tools/slates/example_HD.json create mode 100644 pype/configurations/defaults/presets/tools/sw_folders.json create mode 100644 pype/configurations/defaults/presets/tools/workfiles.json create mode 100644 pype/configurations/defaults/presets/tray/menu_items.json create mode 100644 pype/configurations/defaults/presets/unreal/project_setup.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/celaction/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/config.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/global/create.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/global/filter.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/global/load.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/global/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/maya/create.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/maya/filter.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/maya/load.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/maya/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/nuke/create.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/nuke/load.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/nuke/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/resolve/create.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/test/create.json create mode 100644 pype/configurations/defaults/project_configurations/plugins/test/publish.json create mode 100644 pype/configurations/defaults/studio_configurations/global/applications.json create mode 100644 pype/configurations/defaults/studio_configurations/global/intent.json create mode 100644 pype/configurations/defaults/studio_configurations/global/tools.json create mode 100644 pype/configurations/defaults/studio_configurations/global/tray_modules.json create mode 100644 pype/configurations/defaults/studio_configurations/muster/templates_mapping.json create mode 100644 pype/configurations/defaults/studio_configurations/standalone_publish/families.json diff --git a/pype/configurations/defaults/anatomy/README.md b/pype/configurations/defaults/anatomy/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/configurations/defaults/anatomy/default.yaml b/pype/configurations/defaults/anatomy/default.yaml new file mode 100644 index 0000000000..381aa05d4a --- /dev/null +++ b/pype/configurations/defaults/anatomy/default.yaml @@ -0,0 +1,29 @@ + +version_padding: 3 +version: "v{version:0>{@version_padding}}" +frame_padding: 4 +frame: "{frame:0>{@frame_padding}}" + +work: + folder: "{root}/{project[name]}/{hierarchy}/{asset}/work/{task}" + file: "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}" + path: "{@folder}/{@file}" + +render: + folder: "{root}/{project[name]}/{hierarchy}/{asset}/publish/render/{subset}/{@version}" + file: "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}" + path: "{@folder}/{@file}" + +texture: + path: "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}" + +publish: + folder: "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}" + file: "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}" + path: "{@folder}/{@file}" + thumbnail: "{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}{ext}" + +master: + folder: "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/master" + file: "{project[code]}_{asset}_{subset}_master<_{output}><.{frame}>.{representation}" + path: "{@folder}/{@file}" diff --git a/pype/configurations/defaults/anatomy/roots.json b/pype/configurations/defaults/anatomy/roots.json new file mode 100644 index 0000000000..0282471a60 --- /dev/null +++ b/pype/configurations/defaults/anatomy/roots.json @@ -0,0 +1,5 @@ +{ + "windows": "C:/projects", + "linux": "/mnt/share/projects", + "darwin": "/Volumes/path" +} diff --git a/pype/configurations/defaults/environments/avalon.json b/pype/configurations/defaults/environments/avalon.json new file mode 100644 index 0000000000..832ba07e71 --- /dev/null +++ b/pype/configurations/defaults/environments/avalon.json @@ -0,0 +1,16 @@ +{ + "AVALON_CONFIG": "pype", + "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}", + "AVALON_USERNAME": "avalon", + "AVALON_PASSWORD": "secret", + "AVALON_DEBUG": "1", + "AVALON_MONGO": "mongodb://localhost:2707", + "AVALON_DB": "avalon", + "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_LABEL": "Pype", + "AVALON_TIMEOUT": "1000", + "AVALON_THUMBNAIL_ROOT": "" +} diff --git a/pype/configurations/defaults/environments/blender.json b/pype/configurations/defaults/environments/blender.json new file mode 100644 index 0000000000..6f4f6a012d --- /dev/null +++ b/pype/configurations/defaults/environments/blender.json @@ -0,0 +1,7 @@ +{ + "BLENDER_USER_SCRIPTS": "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", + "{PYTHONPATH}" + ] +} diff --git a/pype/configurations/defaults/environments/celaction.json b/pype/configurations/defaults/environments/celaction.json new file mode 100644 index 0000000000..cdd4e609ab --- /dev/null +++ b/pype/configurations/defaults/environments/celaction.json @@ -0,0 +1,3 @@ +{ + "CELACTION_TEMPLATE": "{PYPE_MODULE_ROOT}/pype/hosts/celaction/celaction_template_scene.scn" +} diff --git a/pype/configurations/defaults/environments/deadline.json b/pype/configurations/defaults/environments/deadline.json new file mode 100644 index 0000000000..e8ef52805b --- /dev/null +++ b/pype/configurations/defaults/environments/deadline.json @@ -0,0 +1,3 @@ +{ + "DEADLINE_REST_URL": "http://localhost:8082" +} diff --git a/pype/configurations/defaults/environments/ftrack.json b/pype/configurations/defaults/environments/ftrack.json new file mode 100644 index 0000000000..4f25de027b --- /dev/null +++ b/pype/configurations/defaults/environments/ftrack.json @@ -0,0 +1,18 @@ +{ + "FTRACK_SERVER": "https://pype.ftrackapp.com", + "FTRACK_ACTIONS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions" + ], + "FTRACK_EVENTS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events" + ], + "PYTHONPATH": [ + "{PYPE_MODULE_ROOT}/pype/vendor", + "{PYTHONPATH}" + ], + "PYBLISHPLUGINPATH": [ + "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" + ], + "FTRACK_EVENTS_MONGO_DB": "pype", + "FTRACK_EVENTS_MONGO_COL": "ftrack_events" +} diff --git a/pype/configurations/defaults/environments/global.json b/pype/configurations/defaults/environments/global.json new file mode 100644 index 0000000000..ef528e6857 --- /dev/null +++ b/pype/configurations/defaults/environments/global.json @@ -0,0 +1,44 @@ +{ + "PYPE_STUDIO_NAME": "Studio Name", + "PYPE_STUDIO_CODE": "stu", + "PYPE_APP_ROOT": "{PYPE_SETUP_PATH}/pypeapp", + "PYPE_MODULE_ROOT": "{PYPE_SETUP_PATH}/repos/pype", + "PYPE_PROJECT_PLUGINS": "", + "STUDIO_SOFT": "{PYP_SETUP_ROOT}/soft", + "FFMPEG_PATH": { + "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/windows/bin", + "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/darwin/bin", + "linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/linux" + }, + "DJV_PATH": { + "windows": [ + "C:/Program Files/djv-1.1.0-Windows-64/bin/djv_view.exe", + "C:/Program Files/DJV/bin/djv_view.exe", + "{STUDIO_SOFT}/djv/windows/bin/djv_view.exe" + ], + "linux": [ + "usr/local/djv/djv_view", + "{STUDIO_SOFT}/djv/linux/bin/djv_view" + ], + "darwin": "Application/DJV.app/Contents/MacOS/DJV" + }, + "PATH": [ + "{PYPE_CONFIG}/launchers", + "{PYPE_APP_ROOT}", + "{FFMPEG_PATH}", + "{PATH}" + ], + "PYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs", + "PYTHONPATH": { + "windows": "{VIRTUAL_ENV}/Lib/site-packages;{PYPE_MODULE_ROOT}/pype/tools;{PYTHONPATH}", + "linux": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYTHONPATH}", + "darwin": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYTHONPATH}" + }, + "PYPE_PROJECT_CONFIGS": "{PYPE_SETUP_PATH}/../studio-project-configs", + "PYPE_PYTHON_EXE": { + "windows": "{VIRTUAL_ENV}/Scripts/python.exe", + "linux": "{VIRTUAL_ENV}/Scripts/python", + "darwin": "{VIRTUAL_ENV}/bin/python" + }, + "PYBLISH_GUI": "pyblish_pype" +} diff --git a/pype/configurations/defaults/environments/harmony.json b/pype/configurations/defaults/environments/harmony.json new file mode 100644 index 0000000000..d394343935 --- /dev/null +++ b/pype/configurations/defaults/environments/harmony.json @@ -0,0 +1,4 @@ +{ + "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1", + "PYBLISH_GUI_ALWAYS_EXEC": "1" +} diff --git a/pype/configurations/defaults/environments/houdini.json b/pype/configurations/defaults/environments/houdini.json new file mode 100644 index 0000000000..95c7d19088 --- /dev/null +++ b/pype/configurations/defaults/environments/houdini.json @@ -0,0 +1,12 @@ +{ + "HOUDINI_PATH": { + "darwin": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "linux": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "windows": "{PYPE_MODULE_ROOT}/setup/houdini;&" + }, + "HOUDINI_MENU_PATH": { + "darwin": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "linux": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "windows": "{PYPE_MODULE_ROOT}/setup/houdini;&" + } +} diff --git a/pype/configurations/defaults/environments/maya.json b/pype/configurations/defaults/environments/maya.json new file mode 100644 index 0000000000..7785b108f7 --- /dev/null +++ b/pype/configurations/defaults/environments/maya.json @@ -0,0 +1,14 @@ +{ + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/maya", + "{PYPE_SETUP_PATH}/repos/maya-look-assigner", + "{PYTHON_ENV}/python2/Lib/site-packages", + "{PYTHONPATH}" + ], + "MAYA_DISABLE_CLIC_IPM": "Yes", + "MAYA_DISABLE_CIP": "Yes", + "MAYA_DISABLE_CER": "Yes", + "PYMEL_SKIP_MEL_INIT": "Yes", + "LC_ALL": "C", + "PYPE_LOG_NO_COLORS": "Yes" +} diff --git a/pype/configurations/defaults/environments/maya_2018.json b/pype/configurations/defaults/environments/maya_2018.json new file mode 100644 index 0000000000..72a0c57ce3 --- /dev/null +++ b/pype/configurations/defaults/environments/maya_2018.json @@ -0,0 +1,11 @@ +{ + "MAYA_VERSION": "2018", + "MAYA_LOCATION": { + "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents", + "linux": "/usr/autodesk/maya{MAYA_VERSION}", + "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MAYA_LOCATION}/MacOS" + } +} diff --git a/pype/configurations/defaults/environments/maya_2020.json b/pype/configurations/defaults/environments/maya_2020.json new file mode 100644 index 0000000000..efd0250bc8 --- /dev/null +++ b/pype/configurations/defaults/environments/maya_2020.json @@ -0,0 +1,11 @@ +{ + "MAYA_VERSION": "2020", + "MAYA_LOCATION": { + "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents", + "linux": "/usr/autodesk/maya{MAYA_VERSION}", + "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MAYA_LOCATION}/MacOS" + } +} diff --git a/pype/configurations/defaults/environments/mayabatch.json b/pype/configurations/defaults/environments/mayabatch.json new file mode 100644 index 0000000000..7785b108f7 --- /dev/null +++ b/pype/configurations/defaults/environments/mayabatch.json @@ -0,0 +1,14 @@ +{ + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/maya", + "{PYPE_SETUP_PATH}/repos/maya-look-assigner", + "{PYTHON_ENV}/python2/Lib/site-packages", + "{PYTHONPATH}" + ], + "MAYA_DISABLE_CLIC_IPM": "Yes", + "MAYA_DISABLE_CIP": "Yes", + "MAYA_DISABLE_CER": "Yes", + "PYMEL_SKIP_MEL_INIT": "Yes", + "LC_ALL": "C", + "PYPE_LOG_NO_COLORS": "Yes" +} diff --git a/pype/configurations/defaults/environments/mayabatch_2019.json b/pype/configurations/defaults/environments/mayabatch_2019.json new file mode 100644 index 0000000000..aa7360a943 --- /dev/null +++ b/pype/configurations/defaults/environments/mayabatch_2019.json @@ -0,0 +1,11 @@ +{ + "MAYA_VERSION": "2019", + "MAYA_LOCATION": { + "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents", + "linux": "/usr/autodesk/maya{MAYA_VERSION}", + "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MAYA_LOCATION}/MacOS" + } +} diff --git a/pype/configurations/defaults/environments/mtoa_3.1.1.json b/pype/configurations/defaults/environments/mtoa_3.1.1.json new file mode 100644 index 0000000000..f7b9f94d4e --- /dev/null +++ b/pype/configurations/defaults/environments/mtoa_3.1.1.json @@ -0,0 +1,23 @@ +{ + "MTOA": "{PYPE_STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}", + "MTOA_VERSION": "3.1.1", + "MAYA_RENDER_DESC_PATH": "{MTOA}", + "MAYA_MODULE_PATH": "{MTOA}", + "ARNOLD_PLUGIN_PATH": "{MTOA}/shaders", + "MTOA_EXTENSIONS_PATH": { + "darwin": "{MTOA}/extensions", + "linux": "{MTOA}/extensions", + "windows": "{MTOA}/extensions" + }, + "MTOA_EXTENSIONS": { + "darwin": "{MTOA}/extensions", + "linux": "{MTOA}/extensions", + "windows": "{MTOA}/extensions" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MTOA}/bin" + }, + "PATH": { + "windows": "{PATH};{MTOA}/bin" + } +} diff --git a/pype/configurations/defaults/environments/muster.json b/pype/configurations/defaults/environments/muster.json new file mode 100644 index 0000000000..26f311146a --- /dev/null +++ b/pype/configurations/defaults/environments/muster.json @@ -0,0 +1,3 @@ +{ + "MUSTER_REST_URL": "http://127.0.0.1:9890" +} diff --git a/pype/configurations/defaults/environments/nuke.json b/pype/configurations/defaults/environments/nuke.json new file mode 100644 index 0000000000..50dd31ac91 --- /dev/null +++ b/pype/configurations/defaults/environments/nuke.json @@ -0,0 +1,15 @@ +{ + "NUKE_PATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/nuke/nuke_path", + "{PYPE_MODULE_ROOT}/setup/nuke/nuke_path", + "{PYPE_STUDIO_PLUGINS}/nuke" + ], + "PATH": { + "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" + }, + "PYPE_LOG_NO_COLORS": "True", + "PYTHONPATH": { + "windows": "{VIRTUAL_ENV}/Lib/site-packages;{PYTHONPATH}", + "linux": "{VIRTUAL_ENV}/lib/python3.6/site-packages:{PYTHONPATH}" + } +} diff --git a/pype/configurations/defaults/environments/nukestudio.json b/pype/configurations/defaults/environments/nukestudio.json new file mode 100644 index 0000000000..b05e2411f0 --- /dev/null +++ b/pype/configurations/defaults/environments/nukestudio.json @@ -0,0 +1,11 @@ +{ + "HIERO_PLUGIN_PATH": [ + "{PYPE_MODULE_ROOT}/setup/nukestudio/hiero_plugin_path" + ], + "PATH": { + "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" + }, + "WORKFILES_STARTUP": "0", + "TAG_ASSETBUILD_STARTUP": "0", + "PYPE_LOG_NO_COLORS": "True" +} diff --git a/pype/configurations/defaults/environments/nukestudio_10.0.json b/pype/configurations/defaults/environments/nukestudio_10.0.json new file mode 100644 index 0000000000..9bdcef53c9 --- /dev/null +++ b/pype/configurations/defaults/environments/nukestudio_10.0.json @@ -0,0 +1,4 @@ +{ + "PYPE_LOG_NO_COLORS": "Yes", + "QT_PREFERRED_BINDING": "PySide" +} \ No newline at end of file diff --git a/pype/configurations/defaults/environments/nukex.json b/pype/configurations/defaults/environments/nukex.json new file mode 100644 index 0000000000..2b77f44076 --- /dev/null +++ b/pype/configurations/defaults/environments/nukex.json @@ -0,0 +1,10 @@ +{ + "NUKE_PATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/nuke/nuke_path", + "{PYPE_MODULE_ROOT}/setup/nuke/nuke_path", + "{PYPE_STUDIO_PLUGINS}/nuke" + ], + "PATH": { + "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" + } +} diff --git a/pype/configurations/defaults/environments/nukex_10.0.json b/pype/configurations/defaults/environments/nukex_10.0.json new file mode 100644 index 0000000000..9bdcef53c9 --- /dev/null +++ b/pype/configurations/defaults/environments/nukex_10.0.json @@ -0,0 +1,4 @@ +{ + "PYPE_LOG_NO_COLORS": "Yes", + "QT_PREFERRED_BINDING": "PySide" +} \ No newline at end of file diff --git a/pype/configurations/defaults/environments/photoshop.json b/pype/configurations/defaults/environments/photoshop.json new file mode 100644 index 0000000000..2208a88665 --- /dev/null +++ b/pype/configurations/defaults/environments/photoshop.json @@ -0,0 +1,4 @@ +{ + "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", + "PYTHONPATH": "{PYTHONPATH}" +} diff --git a/pype/configurations/defaults/environments/premiere.json b/pype/configurations/defaults/environments/premiere.json new file mode 100644 index 0000000000..27dc5c564b --- /dev/null +++ b/pype/configurations/defaults/environments/premiere.json @@ -0,0 +1,11 @@ +{ + "EXTENSIONS_PATH": { + "windows": "{USERPROFILE}/AppData/Roaming/Adobe/CEP/extensions", + "darvin": "{USER}/Library/Application Support/Adobe/CEP/extensions" + }, + "EXTENSIONS_CACHE_PATH": { + "windows": "{USERPROFILE}/AppData/Local/Temp/cep_cache", + "darvin": "{USER}/Library/Application Support/Adobe/CEP/cep_cache" + }, + "installed_zxp": "" +} diff --git a/pype/configurations/defaults/environments/resolve.json b/pype/configurations/defaults/environments/resolve.json new file mode 100644 index 0000000000..1ff197dd5a --- /dev/null +++ b/pype/configurations/defaults/environments/resolve.json @@ -0,0 +1,40 @@ +{ + "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR": [ + "{STUDIO_SOFT}/davinci_resolve/scripts/python" + ], + "RESOLVE_SCRIPT_API": { + "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting", + "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting", + "linux": "/opt/resolve/Developer/Scripting" + }, + "RESOLVE_SCRIPT_LIB": { + "windows": "C:/Program Files/Blackmagic Design/DaVinci Resolve/fusionscript.dll", + "darvin": "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so", + "linux": "/opt/resolve/libs/Fusion/fusionscript.so" + }, + "RESOLVE_UTILITY_SCRIPTS_DIR": { + "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", + "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", + "linux": "/opt/resolve/Fusion/Scripts/Comp" + }, + "PYTHON36_RESOLVE": { + "windows": "{LOCALAPPDATA}/Programs/Python/Python36", + "darvin": "~/Library/Python/3.6/bin", + "linux": "/opt/Python/3.6/bin" + }, + "PYTHONPATH": [ + "{PYTHON36_RESOLVE}/Lib/site-packages", + "{VIRTUAL_ENV}/Lib/site-packages", + "{PYTHONPATH}", + "{RESOLVE_SCRIPT_API}/Modules", + "{PYTHONPATH}" + ], + "PATH": [ + "{PYTHON36_RESOLVE}", + "{PYTHON36_RESOLVE}/Scripts", + "{PATH}" + ], + "PRE_PYTHON_SCRIPT": "{PYPE_MODULE_ROOT}/pype/resolve/preload_console.py", + "PYPE_LOG_NO_COLORS": "True", + "RESOLVE_DEV": "True" +} diff --git a/pype/configurations/defaults/environments/storyboardpro.json b/pype/configurations/defaults/environments/storyboardpro.json new file mode 100644 index 0000000000..581ad4db45 --- /dev/null +++ b/pype/configurations/defaults/environments/storyboardpro.json @@ -0,0 +1,4 @@ +{ + "AVALON_TOONBOOM_WORKFILES_ON_LAUNCH": "1", + "PYBLISH_LITE_ALWAYS_EXEC": "1" +} diff --git a/pype/configurations/defaults/environments/unreal_4.24.json b/pype/configurations/defaults/environments/unreal_4.24.json new file mode 100644 index 0000000000..8feeb0230f --- /dev/null +++ b/pype/configurations/defaults/environments/unreal_4.24.json @@ -0,0 +1,5 @@ +{ + "AVALON_UNREAL_PLUGIN": "{PYPE_SETUP_PATH}/repos/avalon-unreal-integration", + "PYPE_LOG_NO_COLORS": "True", + "QT_PREFERRED_BINDING": "PySide" +} diff --git a/pype/configurations/defaults/environments/vray_4300.json b/pype/configurations/defaults/environments/vray_4300.json new file mode 100644 index 0000000000..3212188441 --- /dev/null +++ b/pype/configurations/defaults/environments/vray_4300.json @@ -0,0 +1,15 @@ +{ + "VRAY_VERSION": "43001", + "VRAY_ROOT": "C:/vray/vray_{VRAY_VERSION}", + "MAYA_RENDER_DESC_PATH": "{VRAY_ROOT}/maya_root/bin/rendererDesc", + "VRAY_FOR_MAYA2019_MAIN": "{VRAY_ROOT}/maya_vray", + "VRAY_FOR_MAYA2019_PLUGINS": "{VRAY_ROOT}/maya_vray/vrayplugins", + "VRAY_PLUGINS": "{VRAY_ROOT}/maya_vray/vrayplugins", + "VRAY_OSL_PATH_MAYA2019": "{VRAY_ROOT}/vray/opensl", + "PATH": "{VRAY_ROOT}/maya_root/bin;{PATH}", + "MAYA_PLUG_IN_PATH": "{VRAY_ROOT}/maya_vray/plug-ins", + "MAYA_SCRIPT_PATH": "{VRAY_ROOT}/maya_vray/scripts", + "PYTHONPATH": "{VRAY_ROOT}/maya_vray/scripts;{PYTHONPATH}", + "XBMLANGPATH": "{VRAY_ROOT}/maya_vray/icons;{XBMLANGPATH}", + "VRAY_AUTH_CLIENT_FILE_PATH": "{VRAY_ROOT}" +} diff --git a/pype/configurations/defaults/launchers/blender_2.80.toml b/pype/configurations/defaults/launchers/blender_2.80.toml new file mode 100644 index 0000000000..5fea78b7b0 --- /dev/null +++ b/pype/configurations/defaults/launchers/blender_2.80.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.80" +schema = "avalon-core:application-1.0" +label = "Blender 2.80" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/configurations/defaults/launchers/blender_2.81.toml b/pype/configurations/defaults/launchers/blender_2.81.toml new file mode 100644 index 0000000000..4f85ee5558 --- /dev/null +++ b/pype/configurations/defaults/launchers/blender_2.81.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.81" +schema = "avalon-core:application-1.0" +label = "Blender 2.81" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/configurations/defaults/launchers/blender_2.82.toml b/pype/configurations/defaults/launchers/blender_2.82.toml new file mode 100644 index 0000000000..840001452e --- /dev/null +++ b/pype/configurations/defaults/launchers/blender_2.82.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.82" +schema = "avalon-core:application-1.0" +label = "Blender 2.82" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/configurations/defaults/launchers/blender_2.83.toml b/pype/configurations/defaults/launchers/blender_2.83.toml new file mode 100644 index 0000000000..7fc8bf87b9 --- /dev/null +++ b/pype/configurations/defaults/launchers/blender_2.83.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.83" +schema = "avalon-core:application-1.0" +label = "Blender 2.83" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/configurations/defaults/launchers/celaction_local.toml b/pype/configurations/defaults/launchers/celaction_local.toml new file mode 100644 index 0000000000..aef3548e08 --- /dev/null +++ b/pype/configurations/defaults/launchers/celaction_local.toml @@ -0,0 +1,8 @@ +executable = "celaction_local" +schema = "avalon-core:application-1.0" +application_dir = "celaction" +label = "CelAction2D" +ftrack_label = "CelAction2D" +icon ="celaction_local" +launch_hook = "pype/hooks/celaction/prelaunch.py/CelactionPrelaunchHook" +ftrack_icon = '{}/app_icons/celaction_local.png' diff --git a/pype/configurations/defaults/launchers/celaction_publish.toml b/pype/configurations/defaults/launchers/celaction_publish.toml new file mode 100644 index 0000000000..86f4ae39e7 --- /dev/null +++ b/pype/configurations/defaults/launchers/celaction_publish.toml @@ -0,0 +1,7 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "celaction_publish" +label = "Shell" + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/configurations/defaults/launchers/darwin/blender_2.82 b/pype/configurations/defaults/launchers/darwin/blender_2.82 new file mode 100644 index 0000000000..8254411ea2 --- /dev/null +++ b/pype/configurations/defaults/launchers/darwin/blender_2.82 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open -a blender $@ diff --git a/pype/configurations/defaults/launchers/darwin/harmony_17 b/pype/configurations/defaults/launchers/darwin/harmony_17 new file mode 100644 index 0000000000..b7eba2c2d0 --- /dev/null +++ b/pype/configurations/defaults/launchers/darwin/harmony_17 @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +DIRNAME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +set >~/environment.tmp +if [ $? -ne -0 ] ; then + echo "ERROR: cannot write to '~/environment.tmp'!" + read -n 1 -s -r -p "Press any key to exit" + return +fi +open -a Terminal.app "$DIRNAME/harmony_17_launch" diff --git a/pype/configurations/defaults/launchers/darwin/harmony_17_launch b/pype/configurations/defaults/launchers/darwin/harmony_17_launch new file mode 100644 index 0000000000..5dcf5db57e --- /dev/null +++ b/pype/configurations/defaults/launchers/darwin/harmony_17_launch @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +source ~/environment.tmp +export $(cut -d= -f1 ~/environment.tmp) +exe="/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" +$PYPE_PYTHON_EXE -c "import avalon.harmony;avalon.harmony.launch('$exe')" diff --git a/pype/configurations/defaults/launchers/darwin/python3 b/pype/configurations/defaults/launchers/darwin/python3 new file mode 100644 index 0000000000..c2b82c7638 --- /dev/null +++ b/pype/configurations/defaults/launchers/darwin/python3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open /usr/bin/python3 --args $@ diff --git a/pype/configurations/defaults/launchers/harmony_17.toml b/pype/configurations/defaults/launchers/harmony_17.toml new file mode 100644 index 0000000000..dbb76444a7 --- /dev/null +++ b/pype/configurations/defaults/launchers/harmony_17.toml @@ -0,0 +1,8 @@ +application_dir = "harmony" +label = "Harmony 17" +ftrack_label = "Harmony" +schema = "avalon-core:application-1.0" +executable = "harmony_17" +description = "" +icon ="harmony_icon" +ftrack_icon = '{}/app_icons/harmony.png' diff --git a/pype/configurations/defaults/launchers/houdini_16.toml b/pype/configurations/defaults/launchers/houdini_16.toml new file mode 100644 index 0000000000..e29fa74cad --- /dev/null +++ b/pype/configurations/defaults/launchers/houdini_16.toml @@ -0,0 +1,7 @@ +executable = "houdini_16" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini 16" +ftrack_label = "Houdini" +icon = "houdini_icon" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/configurations/defaults/launchers/houdini_17.toml b/pype/configurations/defaults/launchers/houdini_17.toml new file mode 100644 index 0000000000..5d01364330 --- /dev/null +++ b/pype/configurations/defaults/launchers/houdini_17.toml @@ -0,0 +1,7 @@ +executable = "houdini_17" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini 17.0" +ftrack_label = "Houdini" +icon = "houdini_icon" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/configurations/defaults/launchers/houdini_18.toml b/pype/configurations/defaults/launchers/houdini_18.toml new file mode 100644 index 0000000000..93b9a3334d --- /dev/null +++ b/pype/configurations/defaults/launchers/houdini_18.toml @@ -0,0 +1,7 @@ +executable = "houdini_18" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini 18" +ftrack_label = "Houdini" +icon = "houdini_icon" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/configurations/defaults/launchers/linux/maya2016 b/pype/configurations/defaults/launchers/linux/maya2016 new file mode 100644 index 0000000000..98424304b1 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/maya2016 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2016/bin/maya" + +if [[ -z $PYPE_LOG_NO_COLORS ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/configurations/defaults/launchers/linux/maya2017 b/pype/configurations/defaults/launchers/linux/maya2017 new file mode 100644 index 0000000000..7a2662a55e --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/maya2017 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2017/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/configurations/defaults/launchers/linux/maya2018 b/pype/configurations/defaults/launchers/linux/maya2018 new file mode 100644 index 0000000000..db832b3fe7 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/maya2018 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2018/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/configurations/defaults/launchers/linux/maya2019 b/pype/configurations/defaults/launchers/linux/maya2019 new file mode 100644 index 0000000000..8398734ab9 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/maya2019 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2019/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/configurations/defaults/launchers/linux/maya2020 b/pype/configurations/defaults/launchers/linux/maya2020 new file mode 100644 index 0000000000..18a1edd598 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/maya2020 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2020/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/configurations/defaults/launchers/linux/nuke11.3 b/pype/configurations/defaults/launchers/linux/nuke11.3 new file mode 100644 index 0000000000..b1c9a90d74 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/nuke11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3' diff --git a/pype/configurations/defaults/launchers/linux/nuke12.0 b/pype/configurations/defaults/launchers/linux/nuke12.0 new file mode 100644 index 0000000000..99ea1a6b0c --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/nuke12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0' diff --git a/pype/configurations/defaults/launchers/linux/nukestudio11.3 b/pype/configurations/defaults/launchers/linux/nukestudio11.3 new file mode 100644 index 0000000000..750d54a7d5 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/nukestudio11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3 --studio' diff --git a/pype/configurations/defaults/launchers/linux/nukestudio12.0 b/pype/configurations/defaults/launchers/linux/nukestudio12.0 new file mode 100644 index 0000000000..ba5cf654a8 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/nukestudio12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0 --studio' diff --git a/pype/configurations/defaults/launchers/linux/nukex11.3 b/pype/configurations/defaults/launchers/linux/nukex11.3 new file mode 100644 index 0000000000..d913e4b961 --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/nukex11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3 -nukex' diff --git a/pype/configurations/defaults/launchers/linux/nukex12.0 b/pype/configurations/defaults/launchers/linux/nukex12.0 new file mode 100644 index 0000000000..da2721c48b --- /dev/null +++ b/pype/configurations/defaults/launchers/linux/nukex12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0 -nukex' diff --git a/pype/configurations/defaults/launchers/maya_2016.toml b/pype/configurations/defaults/launchers/maya_2016.toml new file mode 100644 index 0000000000..d69c4effaf --- /dev/null +++ b/pype/configurations/defaults/launchers/maya_2016.toml @@ -0,0 +1,26 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2016x64" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2016" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" + +[environment] +MAYA_DISABLE_CLIC_IPM = "Yes" # Disable the AdSSO process +MAYA_DISABLE_CIP = "Yes" # Shorten time to boot +MAYA_DISABLE_CER = "Yes" +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/maya_2017.toml b/pype/configurations/defaults/launchers/maya_2017.toml new file mode 100644 index 0000000000..2d1c35b530 --- /dev/null +++ b/pype/configurations/defaults/launchers/maya_2017.toml @@ -0,0 +1,28 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2017" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2017" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" + +[environment] +MAYA_DISABLE_CLIC_IPM = "Yes" # Disable the AdSSO process +MAYA_DISABLE_CIP = "Yes" # Shorten time to boot +MAYA_DISABLE_CER = "Yes" +PYMEL_SKIP_MEL_INIT = "Yes" +LC_ALL= "C" # Mute color management warnings +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/maya_2018.toml b/pype/configurations/defaults/launchers/maya_2018.toml new file mode 100644 index 0000000000..f180263fa2 --- /dev/null +++ b/pype/configurations/defaults/launchers/maya_2018.toml @@ -0,0 +1,14 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya 2018" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2018" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/configurations/defaults/launchers/maya_2019.toml b/pype/configurations/defaults/launchers/maya_2019.toml new file mode 100644 index 0000000000..7ec2cbcedd --- /dev/null +++ b/pype/configurations/defaults/launchers/maya_2019.toml @@ -0,0 +1,14 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya 2019" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2019" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/configurations/defaults/launchers/maya_2020.toml b/pype/configurations/defaults/launchers/maya_2020.toml new file mode 100644 index 0000000000..49d84ef9a0 --- /dev/null +++ b/pype/configurations/defaults/launchers/maya_2020.toml @@ -0,0 +1,14 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya 2020" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2020" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/configurations/defaults/launchers/mayabatch_2019.toml b/pype/configurations/defaults/launchers/mayabatch_2019.toml new file mode 100644 index 0000000000..a928618d2b --- /dev/null +++ b/pype/configurations/defaults/launchers/mayabatch_2019.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2019x64" +schema = "avalon-core:application-1.0" +executable = "mayabatch2019" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/mayabatch_2020.toml b/pype/configurations/defaults/launchers/mayabatch_2020.toml new file mode 100644 index 0000000000..cd1e1e4474 --- /dev/null +++ b/pype/configurations/defaults/launchers/mayabatch_2020.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2020x64" +schema = "avalon-core:application-1.0" +executable = "mayabatch2020" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/mayapy2016.toml b/pype/configurations/defaults/launchers/mayapy2016.toml new file mode 100644 index 0000000000..ad1e3dee86 --- /dev/null +++ b/pype/configurations/defaults/launchers/mayapy2016.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2016x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2016" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/mayapy2017.toml b/pype/configurations/defaults/launchers/mayapy2017.toml new file mode 100644 index 0000000000..8d2095ff47 --- /dev/null +++ b/pype/configurations/defaults/launchers/mayapy2017.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2017x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2017" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/mayapy2018.toml b/pype/configurations/defaults/launchers/mayapy2018.toml new file mode 100644 index 0000000000..597744fd85 --- /dev/null +++ b/pype/configurations/defaults/launchers/mayapy2018.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2018x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2017" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/mayapy2019.toml b/pype/configurations/defaults/launchers/mayapy2019.toml new file mode 100644 index 0000000000..3c8a9860f9 --- /dev/null +++ b/pype/configurations/defaults/launchers/mayapy2019.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2019x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2019" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/mayapy2020.toml b/pype/configurations/defaults/launchers/mayapy2020.toml new file mode 100644 index 0000000000..8f2d2e4a67 --- /dev/null +++ b/pype/configurations/defaults/launchers/mayapy2020.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2020x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2020" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/configurations/defaults/launchers/myapp.toml b/pype/configurations/defaults/launchers/myapp.toml new file mode 100644 index 0000000000..21da0d52b2 --- /dev/null +++ b/pype/configurations/defaults/launchers/myapp.toml @@ -0,0 +1,5 @@ +executable = "python" +schema = "avalon-core:application-1.0" +application_dir = "myapp" +label = "My App" +arguments = [ "-c", "import sys; from Qt import QtWidgets; if __name__ == '__main__':;\n app = QtWidgets.QApplication(sys.argv);\n window = QtWidgets.QWidget();\n window.setWindowTitle(\"My App\");\n window.resize(400, 300);\n window.show();\n app.exec_();\n",] \ No newline at end of file diff --git a/pype/configurations/defaults/launchers/nuke_10.0.toml b/pype/configurations/defaults/launchers/nuke_10.0.toml new file mode 100644 index 0000000000..2195fd3e82 --- /dev/null +++ b/pype/configurations/defaults/launchers/nuke_10.0.toml @@ -0,0 +1,7 @@ +executable = "nuke10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 10.0v4" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nuke_11.0.toml b/pype/configurations/defaults/launchers/nuke_11.0.toml new file mode 100644 index 0000000000..0c981b479a --- /dev/null +++ b/pype/configurations/defaults/launchers/nuke_11.0.toml @@ -0,0 +1,7 @@ +executable = "nuke11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 11.0" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nuke_11.2.toml b/pype/configurations/defaults/launchers/nuke_11.2.toml new file mode 100644 index 0000000000..57c962d126 --- /dev/null +++ b/pype/configurations/defaults/launchers/nuke_11.2.toml @@ -0,0 +1,7 @@ +executable = "nuke11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 11.2" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nuke_11.3.toml b/pype/configurations/defaults/launchers/nuke_11.3.toml new file mode 100644 index 0000000000..87f769c23b --- /dev/null +++ b/pype/configurations/defaults/launchers/nuke_11.3.toml @@ -0,0 +1,7 @@ +executable = "nuke11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 11.3" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nuke_12.0.toml b/pype/configurations/defaults/launchers/nuke_12.0.toml new file mode 100644 index 0000000000..62936b4cdb --- /dev/null +++ b/pype/configurations/defaults/launchers/nuke_12.0.toml @@ -0,0 +1,7 @@ +executable = "nuke12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 12.0" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nukestudio_10.0.toml b/pype/configurations/defaults/launchers/nukestudio_10.0.toml new file mode 100644 index 0000000000..41601e4d40 --- /dev/null +++ b/pype/configurations/defaults/launchers/nukestudio_10.0.toml @@ -0,0 +1,7 @@ +executable = "nukestudio10.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 10.0" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nukestudio_11.0.toml b/pype/configurations/defaults/launchers/nukestudio_11.0.toml new file mode 100644 index 0000000000..7a9d84707a --- /dev/null +++ b/pype/configurations/defaults/launchers/nukestudio_11.0.toml @@ -0,0 +1,7 @@ +executable = "nukestudio11.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 11.0" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nukestudio_11.2.toml b/pype/configurations/defaults/launchers/nukestudio_11.2.toml new file mode 100644 index 0000000000..21557033ca --- /dev/null +++ b/pype/configurations/defaults/launchers/nukestudio_11.2.toml @@ -0,0 +1,7 @@ +executable = "nukestudio11.2" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 11.2" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nukestudio_11.3.toml b/pype/configurations/defaults/launchers/nukestudio_11.3.toml new file mode 100644 index 0000000000..1946ad6c3b --- /dev/null +++ b/pype/configurations/defaults/launchers/nukestudio_11.3.toml @@ -0,0 +1,7 @@ +executable = "nukestudio11.3" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 11.3" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nukestudio_12.0.toml b/pype/configurations/defaults/launchers/nukestudio_12.0.toml new file mode 100644 index 0000000000..4ce7f9b538 --- /dev/null +++ b/pype/configurations/defaults/launchers/nukestudio_12.0.toml @@ -0,0 +1,7 @@ +executable = "nukestudio12.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 12.0" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/configurations/defaults/launchers/nukex_10.0.toml b/pype/configurations/defaults/launchers/nukex_10.0.toml new file mode 100644 index 0000000000..7dee22996d --- /dev/null +++ b/pype/configurations/defaults/launchers/nukex_10.0.toml @@ -0,0 +1,7 @@ +executable = "nukex10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 10.0" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/configurations/defaults/launchers/nukex_11.0.toml b/pype/configurations/defaults/launchers/nukex_11.0.toml new file mode 100644 index 0000000000..c2b4970a26 --- /dev/null +++ b/pype/configurations/defaults/launchers/nukex_11.0.toml @@ -0,0 +1,7 @@ +executable = "nukex11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 11.2" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/configurations/defaults/launchers/nukex_11.2.toml b/pype/configurations/defaults/launchers/nukex_11.2.toml new file mode 100644 index 0000000000..3857b9995c --- /dev/null +++ b/pype/configurations/defaults/launchers/nukex_11.2.toml @@ -0,0 +1,7 @@ +executable = "nukex11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 11.2" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/configurations/defaults/launchers/nukex_11.3.toml b/pype/configurations/defaults/launchers/nukex_11.3.toml new file mode 100644 index 0000000000..56428470eb --- /dev/null +++ b/pype/configurations/defaults/launchers/nukex_11.3.toml @@ -0,0 +1,7 @@ +executable = "nukex11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 11.3" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/configurations/defaults/launchers/nukex_12.0.toml b/pype/configurations/defaults/launchers/nukex_12.0.toml new file mode 100644 index 0000000000..33d7fddb88 --- /dev/null +++ b/pype/configurations/defaults/launchers/nukex_12.0.toml @@ -0,0 +1,7 @@ +executable = "nukex12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 12.0" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/configurations/defaults/launchers/photoshop_2020.toml b/pype/configurations/defaults/launchers/photoshop_2020.toml new file mode 100644 index 0000000000..117b668232 --- /dev/null +++ b/pype/configurations/defaults/launchers/photoshop_2020.toml @@ -0,0 +1,8 @@ +executable = "photoshop_2020" +schema = "avalon-core:application-1.0" +application_dir = "photoshop" +label = "Adobe Photoshop 2020" +icon ="photoshop_icon" +ftrack_label = "Photoshop" +ftrack_icon = '{}/app_icons/photoshop.png' +launch_hook = "pype/hooks/photoshop/prelaunch.py/PhotoshopPrelaunch" diff --git a/pype/configurations/defaults/launchers/premiere_2019.toml b/pype/configurations/defaults/launchers/premiere_2019.toml new file mode 100644 index 0000000000..f4c19c62cb --- /dev/null +++ b/pype/configurations/defaults/launchers/premiere_2019.toml @@ -0,0 +1,8 @@ +executable = "premiere_pro_2019" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC 2019" +icon ="premiere_icon" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/configurations/defaults/launchers/premiere_2020.toml b/pype/configurations/defaults/launchers/premiere_2020.toml new file mode 100644 index 0000000000..4d721c898f --- /dev/null +++ b/pype/configurations/defaults/launchers/premiere_2020.toml @@ -0,0 +1,9 @@ +executable = "premiere_pro_2020" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC 2020" +launch_hook = "pype/hooks/premiere/prelaunch.py/PremierePrelaunch" +icon ="premiere_icon" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/configurations/defaults/launchers/python_2.toml b/pype/configurations/defaults/launchers/python_2.toml new file mode 100644 index 0000000000..e9e8dd7899 --- /dev/null +++ b/pype/configurations/defaults/launchers/python_2.toml @@ -0,0 +1,10 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python" +label = "Python 2" +ftrack_label = "Python" +icon ="python_icon" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/configurations/defaults/launchers/python_3.toml b/pype/configurations/defaults/launchers/python_3.toml new file mode 100644 index 0000000000..5cbd8b2943 --- /dev/null +++ b/pype/configurations/defaults/launchers/python_3.toml @@ -0,0 +1,10 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python3" +label = "Python 3" +ftrack_label = "Python" +icon ="python_icon" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/configurations/defaults/launchers/resolve_16.toml b/pype/configurations/defaults/launchers/resolve_16.toml new file mode 100644 index 0000000000..430fd1a638 --- /dev/null +++ b/pype/configurations/defaults/launchers/resolve_16.toml @@ -0,0 +1,9 @@ +executable = "resolve_16" +schema = "avalon-core:application-1.0" +application_dir = "resolve" +label = "BM DaVinci Resolve 16" +launch_hook = "pype/hooks/resolve/prelaunch.py/ResolvePrelaunch" +icon ="resolve" + +ftrack_label = "BM DaVinci Resolve" +ftrack_icon = '{}/app_icons/resolve.png' diff --git a/pype/configurations/defaults/launchers/shell.toml b/pype/configurations/defaults/launchers/shell.toml new file mode 100644 index 0000000000..959ad392ea --- /dev/null +++ b/pype/configurations/defaults/launchers/shell.toml @@ -0,0 +1,7 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "shell" +label = "Shell" + +[environment] +CREATE_NEW_CONSOLE = "Yes" \ No newline at end of file diff --git a/pype/configurations/defaults/launchers/storyboardpro_7.toml b/pype/configurations/defaults/launchers/storyboardpro_7.toml new file mode 100644 index 0000000000..ce8e96a49d --- /dev/null +++ b/pype/configurations/defaults/launchers/storyboardpro_7.toml @@ -0,0 +1,8 @@ +application_dir = "storyboardpro" +label = "Storyboard Pro 7" +ftrack_label = "Storyboard Pro" +schema = "avalon-core:application-1.0" +executable = "storyboardpro_7" +description = "" +icon ="storyboardpro_icon" +ftrack_icon = '{}/app_icons/storyboardpro.png' diff --git a/pype/configurations/defaults/launchers/unreal_4.24.toml b/pype/configurations/defaults/launchers/unreal_4.24.toml new file mode 100644 index 0000000000..0a799e5dcb --- /dev/null +++ b/pype/configurations/defaults/launchers/unreal_4.24.toml @@ -0,0 +1,8 @@ +executable = "unreal" +schema = "avalon-core:application-1.0" +application_dir = "unreal" +label = "Unreal Editor 4.24" +ftrack_label = "UnrealEditor" +icon ="ue4_icon" +launch_hook = "pype/hooks/unreal/unreal_prelaunch.py/UnrealPrelaunch" +ftrack_icon = '{}/app_icons/ue4.png' diff --git a/pype/configurations/defaults/launchers/windows/blender_2.80.bat b/pype/configurations/defaults/launchers/windows/blender_2.80.bat new file mode 100644 index 0000000000..5b8a37356b --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/blender_2.80.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.80\blender.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/blender_2.81.bat b/pype/configurations/defaults/launchers/windows/blender_2.81.bat new file mode 100644 index 0000000000..a900b18eda --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/blender_2.81.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.81\blender.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/blender_2.82.bat b/pype/configurations/defaults/launchers/windows/blender_2.82.bat new file mode 100644 index 0000000000..7105c1efe1 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/blender_2.82.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.82\blender.exe" --python-use-system-env +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/blender_2.83.bat b/pype/configurations/defaults/launchers/windows/blender_2.83.bat new file mode 100644 index 0000000000..671952f0d7 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/blender_2.83.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.83\blender.exe" --python-use-system-env +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/celaction_local.bat b/pype/configurations/defaults/launchers/windows/celaction_local.bat new file mode 100644 index 0000000000..8f2171617e --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/celaction_local.bat @@ -0,0 +1,19 @@ +set __app__="CelAction2D" +set __app_dir__="C:\Program Files (x86)\CelAction\" +set __exe__="C:\Program Files (x86)\CelAction\CelAction2D.exe" + +if not exist %__exe__% goto :missing_app + +pushd %__app_dir__% + +if "%PYPE_CELACTION_PROJECT_FILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% "%PYPE_CELACTION_PROJECT_FILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/celaction_publish.bat b/pype/configurations/defaults/launchers/windows/celaction_publish.bat new file mode 100644 index 0000000000..77ec2ac24e --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/celaction_publish.bat @@ -0,0 +1,3 @@ +echo %* + +%PYPE_PYTHON_EXE% "%PYPE_MODULE_ROOT%\pype\hosts\celaction\cli.py" %* diff --git a/pype/configurations/defaults/launchers/windows/harmony_17.bat b/pype/configurations/defaults/launchers/windows/harmony_17.bat new file mode 100644 index 0000000000..0822650875 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/harmony_17.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Harmony 17" +set __exe__="C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "python -c ^"import avalon.harmony;avalon.harmony.launch("%__exe__%")^"" + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/houdini_16.bat b/pype/configurations/defaults/launchers/windows/houdini_16.bat new file mode 100644 index 0000000000..018ba08b4c --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/houdini_16.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 16.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 16.0.621\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/houdini_17.bat b/pype/configurations/defaults/launchers/windows/houdini_17.bat new file mode 100644 index 0000000000..950a599623 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/houdini_17.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 17.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 17.0.459\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/houdini_18.bat b/pype/configurations/defaults/launchers/windows/houdini_18.bat new file mode 100644 index 0000000000..3d6b1ae258 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/houdini_18.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 18.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 18.0.287\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/maya2016.bat b/pype/configurations/defaults/launchers/windows/maya2016.bat new file mode 100644 index 0000000000..54f15cf269 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/maya2016.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2016" +set __exe__="C:\Program Files\Autodesk\Maya2016\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/maya2017.bat b/pype/configurations/defaults/launchers/windows/maya2017.bat new file mode 100644 index 0000000000..5c2aeb495c --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/maya2017.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2017" +set __exe__="C:\Program Files\Autodesk\Maya2017\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/maya2018.bat b/pype/configurations/defaults/launchers/windows/maya2018.bat new file mode 100644 index 0000000000..28cf776c77 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/maya2018.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2018" +set __exe__="C:\Program Files\Autodesk\Maya2018\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/maya2019.bat b/pype/configurations/defaults/launchers/windows/maya2019.bat new file mode 100644 index 0000000000..7e80dd2557 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/maya2019.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/maya2020.bat b/pype/configurations/defaults/launchers/windows/maya2020.bat new file mode 100644 index 0000000000..b2acb5df5a --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/maya2020.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2020" +set __exe__="C:\Program Files\Autodesk\maya2020\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayabatch2019.bat b/pype/configurations/defaults/launchers/windows/mayabatch2019.bat new file mode 100644 index 0000000000..ddd9b9b956 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayabatch2019.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Maya Batch 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\mayabatch.exe" +if not exist %__exe__% goto :missing_app + +echo "running maya : %*" +%__exe__% %* +echo "done." +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayabatch2020.bat b/pype/configurations/defaults/launchers/windows/mayabatch2020.bat new file mode 100644 index 0000000000..b1cbc6dbb6 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayabatch2020.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Maya Batch 2020" +set __exe__="C:\Program Files\Autodesk\Maya2020\bin\mayabatch.exe" +if not exist %__exe__% goto :missing_app + +echo "running maya : %*" +%__exe__% %* +echo "done." +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayapy2016.bat b/pype/configurations/defaults/launchers/windows/mayapy2016.bat new file mode 100644 index 0000000000..205991fd3d --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayapy2016.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2016" +set __exe__="C:\Program Files\Autodesk\Maya2016\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayapy2017.bat b/pype/configurations/defaults/launchers/windows/mayapy2017.bat new file mode 100644 index 0000000000..14aacc5a7f --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayapy2017.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2017" +set __exe__="C:\Program Files\Autodesk\Maya2017\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayapy2018.bat b/pype/configurations/defaults/launchers/windows/mayapy2018.bat new file mode 100644 index 0000000000..c47c472f46 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayapy2018.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2018" +set __exe__="C:\Program Files\Autodesk\Maya2018\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayapy2019.bat b/pype/configurations/defaults/launchers/windows/mayapy2019.bat new file mode 100644 index 0000000000..73ca5b2d40 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayapy2019.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/mayapy2020.bat b/pype/configurations/defaults/launchers/windows/mayapy2020.bat new file mode 100644 index 0000000000..770a03dcf5 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/mayapy2020.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2020" +set __exe__="C:\Program Files\Autodesk\Maya2020\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eofS + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nuke10.0.bat b/pype/configurations/defaults/launchers/windows/nuke10.0.bat new file mode 100644 index 0000000000..a47cbdfb20 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nuke10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nuke11.0.bat b/pype/configurations/defaults/launchers/windows/nuke11.0.bat new file mode 100644 index 0000000000..a374c5cf5b --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nuke11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nuke11.2.bat b/pype/configurations/defaults/launchers/windows/nuke11.2.bat new file mode 100644 index 0000000000..4c777ac28c --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nuke11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nuke11.3.bat b/pype/configurations/defaults/launchers/windows/nuke11.3.bat new file mode 100644 index 0000000000..a023f5f46f --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nuke11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nuke12.0.bat b/pype/configurations/defaults/launchers/windows/nuke12.0.bat new file mode 100644 index 0000000000..d8fb5772bb --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nuke12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukestudio10.0.bat b/pype/configurations/defaults/launchers/windows/nukestudio10.0.bat new file mode 100644 index 0000000000..82f833667c --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukestudio10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukestudio11.0.bat b/pype/configurations/defaults/launchers/windows/nukestudio11.0.bat new file mode 100644 index 0000000000..b66797727e --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukestudio11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" -studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukestudio11.2.bat b/pype/configurations/defaults/launchers/windows/nukestudio11.2.bat new file mode 100644 index 0000000000..a653d816b4 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukestudio11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" -studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukestudio11.3.bat b/pype/configurations/defaults/launchers/windows/nukestudio11.3.bat new file mode 100644 index 0000000000..62c8718873 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukestudio11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukestudio12.0.bat b/pype/configurations/defaults/launchers/windows/nukestudio12.0.bat new file mode 100644 index 0000000000..488232bcbf --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukestudio12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukex10.0.bat b/pype/configurations/defaults/launchers/windows/nukex10.0.bat new file mode 100644 index 0000000000..1759706a7b --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukex10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" -nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukex11.0.bat b/pype/configurations/defaults/launchers/windows/nukex11.0.bat new file mode 100644 index 0000000000..b554a7b6fa --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukex11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukex11.2.bat b/pype/configurations/defaults/launchers/windows/nukex11.2.bat new file mode 100644 index 0000000000..a4cb5dec5c --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukex11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukex11.3.bat b/pype/configurations/defaults/launchers/windows/nukex11.3.bat new file mode 100644 index 0000000000..490b55cf4c --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukex11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/nukex12.0.bat b/pype/configurations/defaults/launchers/windows/nukex12.0.bat new file mode 100644 index 0000000000..26adf0d3f1 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/nukex12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/photoshop_2020.bat b/pype/configurations/defaults/launchers/windows/photoshop_2020.bat new file mode 100644 index 0000000000..6b90922ef6 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/photoshop_2020.bat @@ -0,0 +1,15 @@ +@echo off + +set __app__="Photoshop 2020" +set __exe__="C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "%PYPE_PYTHON_EXE% -c ^"import avalon.photoshop;avalon.photoshop.launch("%__exe__%")^"" + +goto :eof + +pause + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/premiere_pro_2019.bat b/pype/configurations/defaults/launchers/windows/premiere_pro_2019.bat new file mode 100644 index 0000000000..4886737d2f --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/premiere_pro_2019.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Adobe Premiere Pro" +set __exe__="C:\Program Files\Adobe\Adobe Premiere Pro CC 2019\Adobe Premiere Pro.exe" +if not exist %__exe__% goto :missing_app + +python -u %PREMIERA_PATH%\init.py +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/premiere_pro_2020.bat b/pype/configurations/defaults/launchers/windows/premiere_pro_2020.bat new file mode 100644 index 0000000000..14662d3be3 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/premiere_pro_2020.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Adobe Premiere Pro" +set __exe__="C:\Program Files\Adobe\Adobe Premiere Pro 2020\Adobe Premiere Pro.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/python3.bat b/pype/configurations/defaults/launchers/windows/python3.bat new file mode 100644 index 0000000000..c7c116fe72 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/python3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Python36" +set __exe__="C:\Python36\python.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/resolve_16.bat b/pype/configurations/defaults/launchers/windows/resolve_16.bat new file mode 100644 index 0000000000..1a5d964e6b --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/resolve_16.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Resolve" +set __appy__="Resolve Python Console" +set __exe__="C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe" +set __py__="%PYTHON36_RESOLVE%/python.exe" + +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* +IF "%RESOLVE_DEV%"=="True" (start %__appy__% %__py__% -i %PRE_PYTHON_SCRIPT%) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/shell.bat b/pype/configurations/defaults/launchers/windows/shell.bat new file mode 100644 index 0000000000..eb0895364f --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/shell.bat @@ -0,0 +1,2 @@ +@echo off +start cmd diff --git a/pype/configurations/defaults/launchers/windows/storyboardpro_7.bat b/pype/configurations/defaults/launchers/windows/storyboardpro_7.bat new file mode 100644 index 0000000000..122edac572 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/storyboardpro_7.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Storyboard Pro 7" +set __exe__="C:/Program Files (x86)/Toon Boom Animation/Toon Boom Storyboard Pro 7/win64/bin/StoryboardPro.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "python -c ^"import avalon.storyboardpro;avalon.storyboardpro.launch("%__exe__%")^"" + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/launchers/windows/unreal.bat b/pype/configurations/defaults/launchers/windows/unreal.bat new file mode 100644 index 0000000000..7771aaa5a5 --- /dev/null +++ b/pype/configurations/defaults/launchers/windows/unreal.bat @@ -0,0 +1,11 @@ +set __app__="Unreal Editor" +set __exe__="%AVALON_CURRENT_UNREAL_ENGINE%\Engine\Binaries\Win64\UE4Editor.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %PYPE_UNREAL_PROJECT_FILE% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/configurations/defaults/presets/colorspace/aces103-cg.json b/pype/configurations/defaults/presets/colorspace/aces103-cg.json new file mode 100644 index 0000000000..dd3fca4c2d --- /dev/null +++ b/pype/configurations/defaults/presets/colorspace/aces103-cg.json @@ -0,0 +1,46 @@ +{ + "resolve": { + + }, + "nukestudio": { + + }, + "nuke": { + "root": { + "colorManagement": "OCIO", + "OCIO_config": "aces_1.0.3", + "workingSpaceLUT": "ACES - ACEScg", + "defaultViewerLUT": "OCIO LUTs", + "monitorLut": "ACES/sRGB", + "int8Lut": "Utility - sRGB - Texture", + "int16Lut": "Utility - sRGB - Texture", + "logLut": "Input - ADX - ADX10", + "floatLut": "ACES - ACES2065-1" + }, + "viewer": { + "viewerProcess": "sRGB (ACES)" + }, + "write": { + "render": { + "colorspace": "ACES - ACES2065-1" + }, + "prerender": { + "colorspace": "ACES - ACES2065-1" + }, + "still": { + "colorspace": "Utility - Curve - sRGB" + } + }, + "read": { + "[^-a-zA-Z0-9](beauty)[^-a-zA-Z0-9]": "lin_srgb", + "[^-a-zA-Z0-9](P|N|Z|crypto)[^-a-zA-Z0-9]": "raw", + "[^-a-zA-Z0-9](plateRef)[^-a-zA-Z0-9]": "crv_srgb" + } + }, + "maya": { + + }, + "houdini": { + + } +} diff --git a/pype/configurations/defaults/presets/colorspace/default.json b/pype/configurations/defaults/presets/colorspace/default.json new file mode 100644 index 0000000000..8b934f810d --- /dev/null +++ b/pype/configurations/defaults/presets/colorspace/default.json @@ -0,0 +1,42 @@ +{ + "nuke": { + "root": { + "colorManagement": "Nuke", + "OCIO_config": "nuke-default", + "defaultViewerLUT": "Nuke Root LUTs", + "monitorLut": "sRGB", + "int8Lut": "sRGB", + "int16Lut": "sRGB", + "logLut": "Cineon", + "floatLut": "linear" + }, + "viewer": { + "viewerProcess": "sRGB" + }, + "write": { + "render": { + "colorspace": "linear" + }, + "prerender": { + "colorspace": "linear" + }, + "still": { + "colorspace": "sRGB" + } + }, + "read": { + "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]": "linear", + "[^-a-zA-Z0-9](P|N|Z|crypto)[^-a-zA-Z0-9]": "linear", + "[^-a-zA-Z0-9](plateRef)[^-a-zA-Z0-9]": "sRGB" + } + }, + "maya": { + + }, + "houdini": { + + }, + "resolve": { + + } +} diff --git a/pype/configurations/defaults/presets/dataflow/aces-exr.json b/pype/configurations/defaults/presets/dataflow/aces-exr.json new file mode 100644 index 0000000000..75846c0bd6 --- /dev/null +++ b/pype/configurations/defaults/presets/dataflow/aces-exr.json @@ -0,0 +1,58 @@ +{ + "nuke": { + "nodes": { + "connected": true, + "modifymetadata": { + "_id": "connect_metadata", + "_previous": "ENDING", + "metadata.set.pype_studio_name": "{PYPE_STUDIO_NAME}", + "metadata.set.avalon_project_name": "{AVALON_PROJECT}", + "metadata.set.avalon_project_code": "{PYPE_STUDIO_CODE}", + "metadata.set.avalon_asset_name": "{AVALON_ASSET}" + }, + "crop": { + "_id": "connect_crop", + "_previous": "connect_metadata", + "box": [ + "{metadata.crop.x}", + "{metadata.crop.y}", + "{metadata.crop.right}", + "{metadata.crop.top}" + ] + }, + "write": { + "render": { + "_id": "output_write", + "_previous": "connect_crop", + "file_type": "exr", + "datatype": "16 bit half", + "compression": "Zip (1 scanline)", + "create_directories": true, + "autocrop": true, + "tile_color": "0xff0000ff", + "channels": "rgb" + }, + "prerender": { + "_id": "output_write", + "_previous": "connect_crop", + "file_type": "exr", + "datatype": "16 bit half", + "compression": "Zip (1 scanline)", + "create_directories": true, + "autocrop": false, + "tile_color": "0xc9892aff", + "channels": "rgba" + }, + "still": { + "_previous": "connect_crop", + "channels": "rgba", + "file_type": "tiff", + "datatype": "16 bit", + "compression": "LZW", + "create_directories": true, + "tile_color": "0x4145afff" + } + } + } + } +} diff --git a/pype/configurations/defaults/presets/dataflow/default.json b/pype/configurations/defaults/presets/dataflow/default.json new file mode 100644 index 0000000000..d2f470b5bc --- /dev/null +++ b/pype/configurations/defaults/presets/dataflow/default.json @@ -0,0 +1,55 @@ +{ + "nuke": { + "nodes": { + "connected": true, + "modifymetadata": { + "_id": "connect_metadata", + "_previous": "ENDING", + "metadata.set.pype_studio_name": "{PYPE_STUDIO_NAME}", + "metadata.set.avalon_project_name": "{AVALON_PROJECT}", + "metadata.set.avalon_project_code": "{PYPE_STUDIO_CODE}", + "metadata.set.avalon_asset_name": "{AVALON_ASSET}" + }, + "crop": { + "_id": "connect_crop", + "_previous": "connect_metadata", + "box": [ + "{metadata.crop.x}", + "{metadata.crop.y}", + "{metadata.crop.right}", + "{metadata.crop.top}" + ] + }, + "write": { + "render": { + "_id": "output_write", + "_previous": "connect_crop", + "file_type": "exr", + "datatype": "16 bit half", + "compression": "Zip (1 scanline)", + "autocrop": true, + "tile_color": "0xff0000ff", + "channels": "rgb" + }, + "prerender": { + "_id": "output_write", + "_previous": "connect_crop", + "file_type": "exr", + "datatype": "16 bit half", + "compression": "Zip (1 scanline)", + "autocrop": false, + "tile_color": "0xc9892aff", + "channels": "rgba" + }, + "still": { + "_previous": "connect_crop", + "channels": "rgba", + "file_type": "tiff", + "datatype": "16 bit", + "compression": "LZW", + "tile_color": "0x4145afff" + } + } + } + } +} diff --git a/pype/configurations/defaults/presets/ftrack/ftrack_config.json b/pype/configurations/defaults/presets/ftrack/ftrack_config.json new file mode 100644 index 0000000000..1ef3a9d69f --- /dev/null +++ b/pype/configurations/defaults/presets/ftrack/ftrack_config.json @@ -0,0 +1,16 @@ +{ + "sync_to_avalon": { + "statuses_name_change": ["not ready", "ready"] + }, + + "status_update": { + "_ignore_": ["in progress", "ommited", "on hold"], + "Ready": ["not ready"], + "In Progress" : ["_any_"] + }, + "status_version_to_task": { + "__description__": "Status `from` (key) must be lowered!", + "in progress": "in progress", + "approved": "approved" + } +} diff --git a/pype/configurations/defaults/presets/ftrack/ftrack_custom_attributes.json b/pype/configurations/defaults/presets/ftrack/ftrack_custom_attributes.json new file mode 100644 index 0000000000..f03d473cd0 --- /dev/null +++ b/pype/configurations/defaults/presets/ftrack/ftrack_custom_attributes.json @@ -0,0 +1,165 @@ +[{ + "label": "FPS", + "key": "fps", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "write_security_role": ["ALL"], + "read_security_role": ["ALL"], + "default": null, + "config": { + "isdecimal": true + } +}, { + "label": "Applications", + "key": "applications", + "type": "enumerator", + "entity_type": "show", + "group": "avalon", + "config": { + "multiselect": true, + "data": [ + {"blender_2.80": "Blender 2.80"}, + {"blender_2.81": "Blender 2.81"}, + {"blender_2.82": "Blender 2.82"}, + {"blender_2.83": "Blender 2.83"}, + {"celaction_local": "CelAction2D Local"}, + {"maya_2017": "Maya 2017"}, + {"maya_2018": "Maya 2018"}, + {"maya_2019": "Maya 2019"}, + {"nuke_10.0": "Nuke 10.0"}, + {"nuke_11.2": "Nuke 11.2"}, + {"nuke_11.3": "Nuke 11.3"}, + {"nuke_12.0": "Nuke 12.0"}, + {"nukex_10.0": "NukeX 10.0"}, + {"nukex_11.2": "NukeX 11.2"}, + {"nukex_11.3": "NukeX 11.3"}, + {"nukex_12.0": "NukeX 12.0"}, + {"nukestudio_10.0": "NukeStudio 10.0"}, + {"nukestudio_11.2": "NukeStudio 11.2"}, + {"nukestudio_11.3": "NukeStudio 11.3"}, + {"nukestudio_12.0": "NukeStudio 12.0"}, + {"harmony_17": "Harmony 17"}, + {"houdini_16.5": "Houdini 16.5"}, + {"houdini_17": "Houdini 17"}, + {"houdini_18": "Houdini 18"}, + {"photoshop_2020": "Photoshop 2020"}, + {"python_3": "Python 3"}, + {"python_2": "Python 2"}, + {"premiere_2019": "Premiere Pro 2019"}, + {"premiere_2020": "Premiere Pro 2020"}, + {"resolve_16": "BM DaVinci Resolve 16"} + ] + } +}, { + "label": "Avalon auto-sync", + "key": "avalon_auto_sync", + "type": "boolean", + "entity_type": "show", + "group": "avalon", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] +}, { + "label": "Intent", + "key": "intent", + "type": "enumerator", + "entity_type": "assetversion", + "group": "avalon", + "config": { + "multiselect": false, + "data": [ + {"test": "Test"}, + {"wip": "WIP"}, + {"final": "Final"} + ] + } +}, { + "label": "Library Project", + "key": "library_project", + "type": "boolean", + "entity_type": "show", + "group": "avalon", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] +}, { + "label": "Clip in", + "key": "clipIn", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Clip out", + "key": "clipOut", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Frame start", + "key": "frameStart", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Frame end", + "key": "frameEnd", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Tools", + "key": "tools_env", + "type": "enumerator", + "is_hierarchical": true, + "group": "avalon", + "config": { + "multiselect": true, + "data": [ + {"mtoa_3.0.1": "mtoa_3.0.1"}, + {"mtoa_3.1.1": "mtoa_3.1.1"}, + {"mtoa_3.2.0": "mtoa_3.2.0"}, + {"yeti_2.1.2": "yeti_2.1"} + ] + } +}, { + "label": "Resolution Width", + "key": "resolutionWidth", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Resolution Height", + "key": "resolutionHeight", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Pixel aspect", + "key": "pixelAspect", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "config": { + "isdecimal": true + } +}, { + "label": "Frame handles start", + "key": "handleStart", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Frame handles end", + "key": "handleEnd", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +} +] diff --git a/pype/configurations/defaults/presets/ftrack/partnership_ftrack_cred.json b/pype/configurations/defaults/presets/ftrack/partnership_ftrack_cred.json new file mode 100644 index 0000000000..6b3a32f181 --- /dev/null +++ b/pype/configurations/defaults/presets/ftrack/partnership_ftrack_cred.json @@ -0,0 +1,5 @@ +{ + "server_url": "", + "api_key": "", + "api_user": "" +} diff --git a/pype/configurations/defaults/presets/ftrack/plugins/server.json b/pype/configurations/defaults/presets/ftrack/plugins/server.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/presets/ftrack/plugins/server.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/presets/ftrack/plugins/user.json b/pype/configurations/defaults/presets/ftrack/plugins/user.json new file mode 100644 index 0000000000..1ba8e9b511 --- /dev/null +++ b/pype/configurations/defaults/presets/ftrack/plugins/user.json @@ -0,0 +1,5 @@ +{ + "TestAction": { + "ignore_me": true + } +} diff --git a/pype/configurations/defaults/presets/ftrack/project_defaults.json b/pype/configurations/defaults/presets/ftrack/project_defaults.json new file mode 100644 index 0000000000..a4e3aa3362 --- /dev/null +++ b/pype/configurations/defaults/presets/ftrack/project_defaults.json @@ -0,0 +1,18 @@ +{ + "fps": 25, + "frameStart": 1001, + "frameEnd": 1100, + "clipIn": 1001, + "clipOut": 1100, + "handleStart": 10, + "handleEnd": 10, + + "resolutionHeight": 1080, + "resolutionWidth": 1920, + "pixelAspect": 1.0, + "applications": [ + "maya_2019", "nuke_11.3", "nukex_11.3", "nukestudio_11.3", "deadline" + ], + "tools_env": [], + "avalon_auto_sync": true +} diff --git a/pype/configurations/defaults/presets/init.json b/pype/configurations/defaults/presets/init.json new file mode 100644 index 0000000000..361ee7445b --- /dev/null +++ b/pype/configurations/defaults/presets/init.json @@ -0,0 +1,4 @@ +{ + "colorspace": "default", + "dataflow": "default" +} diff --git a/pype/configurations/defaults/presets/maya/capture.json b/pype/configurations/defaults/presets/maya/capture.json new file mode 100644 index 0000000000..b6c4893034 --- /dev/null +++ b/pype/configurations/defaults/presets/maya/capture.json @@ -0,0 +1,108 @@ +{ + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "backgroundBottom": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "backgroundTop": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true + }, + "IO": { + "name": "", + "open_finished": false, + "raw_frame_numbers": false, + "recent_playblasts": [], + "save_file": false + }, + "PanZoom": { + "pan_zoom": true + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "height": 1080, + "mode": "Custom", + "percent": 1.0, + "width": 1920 + }, + "Time Range": { + "end_frame": 25, + "frame": "", + "start_frame": 0, + "time": "Time Slider" + }, + "Viewport Options": { + "cameras": false, + "clipGhosts": false, + "controlVertices": false, + "deformers": false, + "dimensions": false, + "displayLights": 0, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "gpuCacheDisplayFilter": false, + "greasePencils": false, + "grid": false, + "hairSystems": false, + "handles": false, + "high_quality": true, + "hud": false, + "hulls": false, + "ikHandles": false, + "imagePlane": false, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "nurbsCurves": false, + "nurbsSurfaces": false, + "override_viewport_options": true, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": true, + "shadows": false, + "strokes": false, + "subdivSurfaces": false, + "textures": false, + "twoSidedLighting": true + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } +} diff --git a/pype/configurations/defaults/presets/muster/templates_mapping.json b/pype/configurations/defaults/presets/muster/templates_mapping.json new file mode 100644 index 0000000000..4edab9077d --- /dev/null +++ b/pype/configurations/defaults/presets/muster/templates_mapping.json @@ -0,0 +1,19 @@ +{ + "3delight": 41, + "arnold": 46, + "arnold_sf": 57, + "gelato": 30, + "harware": 3, + "krakatoa": 51, + "file_layers": 7, + "mentalray": 2, + "mentalray_sf": 6, + "redshift": 55, + "renderman": 29, + "software": 1, + "software_sf": 5, + "turtle": 10, + "vector": 4, + "vray": 37, + "ffmpeg": 48 +} diff --git a/pype/configurations/defaults/presets/nukestudio/tags.json b/pype/configurations/defaults/presets/nukestudio/tags.json new file mode 100644 index 0000000000..56fcfcbce9 --- /dev/null +++ b/pype/configurations/defaults/presets/nukestudio/tags.json @@ -0,0 +1,262 @@ +{ + "Hierarchy": { + "editable": "1", + "note": "{folder}/{sequence}/{shot}", + "icon": { + "path": "hierarchy.png" + }, + "metadata": { + "folder": "FOLDER_NAME", + "shot": "{clip}", + "track": "{track}", + "sequence": "{sequence}", + "episode": "EPISODE_NAME", + "root": "{projectroot}" + } + }, + "Source Resolution": { + "editable": "1", + "note": "Use source resolution", + "icon": { + "path": "resolution.png" + }, + "metadata": { + "family": "resolution" + } + }, + "Retiming": { + "editable": "1", + "note": "Clip has retime or TimeWarp effects (or multiple effects stacked on the clip)", + "icon": { + "path": "retiming.png" + }, + "metadata": { + "family": "retiming", + "marginIn": 1, + "marginOut": 1 + } + }, + "Frame start": { + "editable": "1", + "note": "Starting frame for comps. \n\n> Use `value` and add either number or write `source` (if you want to preserve source frame numbering)", + "icon": { + "path": "icons:TagBackground.png" + }, + "metadata": { + "family": "frameStart", + "value": "1001" + } + }, + "[Lenses]": { + "Set lense here": { + "editable": "1", + "note": "Adjust parameters of your lense and then drop to clip. Remember! You can always overwrite on clip", + "icon": { + "path": "lense.png" + }, + "metadata": { + "focalLengthMm": 57 + + } + } + }, + "[Subsets]": { + "Audio": { + "editable": "1", + "note": "Export with Audio", + "icon": { + "path": "volume.png" + }, + "metadata": { + "family": "audio", + "subset": "main" + } + }, + "plateFg": { + "editable": "1", + "note": "Add to publish to \"forground\" subset. Change metadata subset name if different order number", + "icon": { + "path": "z_layer_fg.png" + }, + "metadata": { + "family": "plate", + "subset": "Fg01" + } + }, + "plateBg": { + "editable": "1", + "note": "Add to publish to \"background\" subset. Change metadata subset name if different order number", + "icon": { + "path": "z_layer_bg.png" + }, + "metadata": { + "family": "plate", + "subset": "Bg01" + } + }, + "plateRef": { + "editable": "1", + "note": "Add to publish to \"reference\" subset.", + "icon": { + "path": "icons:Reference.png" + }, + "metadata": { + "family": "plate", + "subset": "Ref" + } + }, + "plateMain": { + "editable": "1", + "note": "Add to publish to \"main\" subset.", + "icon": { + "path": "z_layer_main.png" + }, + "metadata": { + "family": "plate", + "subset": "main" + } + }, + "plateProxy": { + "editable": "1", + "note": "Add to publish to \"proxy\" subset.", + "icon": { + "path": "z_layer_main.png" + }, + "metadata": { + "family": "plate", + "subset": "proxy" + } + }, + "review": { + "editable": "1", + "note": "Upload to Ftrack as review component.", + "icon": { + "path": "review.png" + }, + "metadata": { + "family": "review", + "track": "review" + } + } + }, + "[Handles]": { + "start: add 20 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "20", + "args": "{'op':'add','where':'start'}" + } + }, + "start: add 10 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "10", + "args": "{'op':'add','where':'start'}" + } + }, + "start: add 5 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "5", + "args": "{'op':'add','where':'start'}" + } + }, + "start: add 0 frames": { + "editable": "1", + "note": "Adding frames to start of selected clip", + "icon": { + "path": "3_add_handles_start.png" + }, + "metadata": { + "family": "handles", + "value": "0", + "args": "{'op':'add','where':'start'}" + } + }, + "end: add 20 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "20", + "args": "{'op':'add','where':'end'}" + } + }, + "end: add 10 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "10", + "args": "{'op':'add','where':'end'}" + } + }, + "end: add 5 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "5", + "args": "{'op':'add','where':'end'}" + } + }, + "end: add 0 frames": { + "editable": "1", + "note": "Adding frames to end of selected clip", + "icon": { + "path": "1_add_handles_end.png" + }, + "metadata": { + "family": "handles", + "value": "0", + "args": "{'op':'add','where':'end'}" + } + } + }, + "NukeScript": { + "editable": "1", + "note": "Collecting track items to Nuke scripts.", + "icon": { + "path": "icons:TagNuke.png" + }, + "metadata": { + "family": "nukescript", + "subset": "main" + } + }, + "Comment": { + "editable": "1", + "note": "Comment on a shot.", + "icon": { + "path": "icons:TagComment.png" + }, + "metadata": { + "family": "comment", + "subset": "main" + } + } +} diff --git a/pype/configurations/defaults/presets/plugins/celaction/publish.json b/pype/configurations/defaults/presets/plugins/celaction/publish.json new file mode 100644 index 0000000000..e791f574d9 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/celaction/publish.json @@ -0,0 +1,10 @@ +{ + "ExtractCelactionDeadline": { + "deadline_department": "", + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_group": "", + "deadline_chunk_size": 10 + } +} diff --git a/pype/configurations/defaults/presets/plugins/config.json b/pype/configurations/defaults/presets/plugins/config.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/configurations/defaults/presets/plugins/ftrack/publish.json b/pype/configurations/defaults/presets/plugins/ftrack/publish.json new file mode 100644 index 0000000000..d0469ae4f7 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/ftrack/publish.json @@ -0,0 +1,6 @@ +{ + "IntegrateFtrackNote": { + "note_with_intent_template": "{intent}: {comment}", + "note_labels": [] + } +} diff --git a/pype/configurations/defaults/presets/plugins/global/create.json b/pype/configurations/defaults/presets/plugins/global/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/global/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/presets/plugins/global/filter.json b/pype/configurations/defaults/presets/plugins/global/filter.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/global/filter.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/presets/plugins/global/load.json b/pype/configurations/defaults/presets/plugins/global/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/global/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/presets/plugins/global/publish.json b/pype/configurations/defaults/presets/plugins/global/publish.json new file mode 100644 index 0000000000..016868fc92 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/global/publish.json @@ -0,0 +1,86 @@ +{ + "IntegrateMasterVersion": { + "enabled": false + }, + "ExtractJpegEXR": { + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "output": [] + } + }, + "ExtractReview": { + "__documentation__": "http://pype.club/docs/admin_presets_plugins", + "profiles": [ + { + "families": [], + "hosts": [], + "outputs": { + "h264": { + "filter": { + "families": ["render", "review", "ftrack"] + }, + "ext": "mp4", + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "video_filters": [], + "audio_filters": [], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + }, + "tags": ["burnin", "ftrackreview"] + } + } + } + ] + }, + "ExtractBurnin": { + "options": { + "opacity": 1, + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5, + "bg_opacity": 0.5, + "font_size": 42 + }, + "fields": { + + }, + "profiles": [ + { + "burnins": { + "burnin": { + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_RIGHT": "{anatomy[version]}", + "TOP_CENTERED": "", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "BOTTOM_CENTERED": "{asset}", + "BOTTOM_LEFT": "{username}" + } + } + } + ] + }, + "IntegrateAssetNew": { + "template_name_profiles": { + "publish": { + "families": [], + "tasks": [] + }, + "render": { + "families": ["review", "render", "prerender"] + } + } + }, + "ProcessSubmittedJobOnFarm": { + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "" + } +} diff --git a/pype/configurations/defaults/presets/plugins/maya/create.json b/pype/configurations/defaults/presets/plugins/maya/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/maya/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/presets/plugins/maya/filter.json b/pype/configurations/defaults/presets/plugins/maya/filter.json new file mode 100644 index 0000000000..83d6f05f31 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/maya/filter.json @@ -0,0 +1,9 @@ +{ + "Preset n1": { + "ValidateNoAnimation": false, + "ValidateShapeDefaultNames": false + }, + "Preset n2": { + "ValidateNoAnimation": false + } +} diff --git a/pype/configurations/defaults/presets/plugins/maya/load.json b/pype/configurations/defaults/presets/plugins/maya/load.json new file mode 100644 index 0000000000..260fbb35ee --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/maya/load.json @@ -0,0 +1,18 @@ +{ + "colors": { + "model": [0.821, 0.518, 0.117], + "rig": [0.144, 0.443, 0.463], + "pointcache": [0.368, 0.821, 0.117], + "animation": [0.368, 0.821, 0.117], + "ass": [1.0, 0.332, 0.312], + "camera": [0.447, 0.312, 1.0], + "fbx": [1.0, 0.931, 0.312], + "mayaAscii": [0.312, 1.0, 0.747], + "setdress": [0.312, 1.0, 0.747], + "layout": [0.312, 1.0, 0.747], + "vdbcache": [0.312, 1.0, 0.428], + "vrayproxy": [0.258, 0.95, 0.541], + "yeticache": [0.2, 0.8, 0.3], + "yetiRig": [0, 0.8, 0.5] + } +} diff --git a/pype/configurations/defaults/presets/plugins/maya/publish.json b/pype/configurations/defaults/presets/plugins/maya/publish.json new file mode 100644 index 0000000000..2e2b3164f3 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/maya/publish.json @@ -0,0 +1,17 @@ +{ + "ValidateModelName": { + "enabled": false, + "material_file": "/path/to/shader_name_definition.txt", + "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" + }, + "ValidateAssemblyName": { + "enabled": false + }, + "ValidateShaderName": { + "enabled": false, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": false + } +} diff --git a/pype/configurations/defaults/presets/plugins/maya/workfile_build.json b/pype/configurations/defaults/presets/plugins/maya/workfile_build.json new file mode 100644 index 0000000000..2872b783cb --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/maya/workfile_build.json @@ -0,0 +1,54 @@ +[{ + "tasks": ["lighting"], + + "current_context": [{ + "subset_name_filters": [".+[Mm]ain"], + "families": ["model"], + "repre_names": ["abc", "ma"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["animation", "pointcache"], + "repre_names": ["abc"], + "loaders": ["ReferenceLoader"] + },{ + "families": ["rendersetup"], + "repre_names": ["json"], + "loaders": ["RenderSetupLoader"] + }, { + "families": ["camera"], + "repre_names": ["abc"], + "loaders": ["ReferenceLoader"] + }], + + "linked_assets": [{ + "families": ["setdress"], + "repre_names": ["ma"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["ass"], + "repre_names": ["ass"], + "loaders":["assLoader"] + }] +}, { + "tasks": ["animation"], + + "current_context": [{ + "families": ["camera"], + "repre_names": ["abc", "ma"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["audio"], + "repre_names": ["wav"], + "loaders": ["RenderSetupLoader"] + }], + + "linked_assets": [{ + "families": ["setdress"], + "repre_names": ["proxy"], + "loaders": ["ReferenceLoader"] + }, { + "families": ["rig"], + "repre_names": ["ass"], + "loaders": ["rigLoader"] + }] +}] diff --git a/pype/configurations/defaults/presets/plugins/nuke/create.json b/pype/configurations/defaults/presets/plugins/nuke/create.json new file mode 100644 index 0000000000..4deb0b4ad5 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/nuke/create.json @@ -0,0 +1,8 @@ +{ + "CreateWriteRender": { + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + }, + "CreateWritePrerender": { + "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + } +} diff --git a/pype/configurations/defaults/presets/plugins/nuke/load.json b/pype/configurations/defaults/presets/plugins/nuke/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/nuke/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/presets/plugins/nuke/publish.json b/pype/configurations/defaults/presets/plugins/nuke/publish.json new file mode 100644 index 0000000000..ab0d0e76a5 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/nuke/publish.json @@ -0,0 +1,48 @@ +{ + "ExtractThumbnail": { + "nodes": { + "Reformat": [ + ["type", "to format"], + ["format", "HD_1080"], + ["filter", "Lanczos6"], + ["black_outside", true], + ["pbb", false] + ] + } + }, + "ValidateNukeWriteKnobs": { + "enabled": false, + "knobs": { + "render": { + "review": true + } + } + }, + "ExtractReviewDataLut": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", + "enabled": "keep in on `false` if you want Nuke baking colorspace workflow applied else FFmpeg will convert imgsequence with baked LUT" + }, + "enabled": false + }, + "ExtractReviewDataMov": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", + "enabled": "keep in on `true` if you want Nuke baking colorspace workflow applied" + }, + "enabled": true, + "viewer_lut_raw": false + }, + "ExtractSlateFrame": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`" + }, + "viewer_lut_raw": false + }, + "NukeSubmitDeadline": { + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_chunk_size": 1 + } +} diff --git a/pype/configurations/defaults/presets/plugins/nuke/workfile_build.json b/pype/configurations/defaults/presets/plugins/nuke/workfile_build.json new file mode 100644 index 0000000000..d3613c929e --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/nuke/workfile_build.json @@ -0,0 +1,11 @@ +[{ + "tasks": ["compositing"], + + "current_context": [{ + "families": ["render", "plate"], + "repre_names": ["exr" ,"dpx"], + "loaders": ["LoadSequence"] + }], + + "linked_assets": [] +}] diff --git a/pype/configurations/defaults/presets/plugins/nukestudio/filter.json b/pype/configurations/defaults/presets/plugins/nukestudio/filter.json new file mode 100644 index 0000000000..bd6a0dc1bd --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/nukestudio/filter.json @@ -0,0 +1,10 @@ +{ + "strict": { + "ValidateVersion": true, + "VersionUpWorkfile": true + }, + "benevolent": { + "ValidateVersion": false, + "VersionUpWorkfile": false + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/presets/plugins/nukestudio/publish.json b/pype/configurations/defaults/presets/plugins/nukestudio/publish.json new file mode 100644 index 0000000000..8c4ad133f1 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/nukestudio/publish.json @@ -0,0 +1,8 @@ +{ + "CollectInstanceVersion": { + "enabled": false + }, + "ExtractReviewCutUpVideo": { + "tags_addition": [] + } +} diff --git a/pype/configurations/defaults/presets/plugins/resolve/create.json b/pype/configurations/defaults/presets/plugins/resolve/create.json new file mode 100644 index 0000000000..29ca5900fb --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/resolve/create.json @@ -0,0 +1,7 @@ +{ + "CreateShotClip": { + "clipName": "{track}{sequence}{shot}", + "folder": "takes", + "steps": 20 + } +} diff --git a/pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json b/pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json new file mode 100644 index 0000000000..2b2fb660c2 --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json @@ -0,0 +1,25 @@ +{ + "ExtractThumbnailSP": { + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "output": [] + } + }, + "ExtractReviewSP": { + "outputs": { + "h264": { + "input": [ + "-gamma 2.2" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18" + ], + "tags": ["preview"], + "ext": "mov" + } + } + } +} diff --git a/pype/configurations/defaults/presets/plugins/test/create.json b/pype/configurations/defaults/presets/plugins/test/create.json new file mode 100644 index 0000000000..fa0b2fc05f --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/test/create.json @@ -0,0 +1,8 @@ +{ + "MyTestCreator": { + "my_test_property": "B", + "active": false, + "new_property": "new", + "family": "new_family" + } +} diff --git a/pype/configurations/defaults/presets/plugins/test/publish.json b/pype/configurations/defaults/presets/plugins/test/publish.json new file mode 100644 index 0000000000..3180dd5d8a --- /dev/null +++ b/pype/configurations/defaults/presets/plugins/test/publish.json @@ -0,0 +1,10 @@ +{ + "MyTestPlugin": { + "label": "loaded from preset", + "optional": true, + "families": ["changed", "by", "preset"] + }, + "MyTestRemovedPlugin": { + "enabled": false + } +} diff --git a/pype/configurations/defaults/presets/premiere/asset_default.json b/pype/configurations/defaults/presets/premiere/asset_default.json new file mode 100644 index 0000000000..84d2bde3d8 --- /dev/null +++ b/pype/configurations/defaults/presets/premiere/asset_default.json @@ -0,0 +1,5 @@ +{ + "frameStart": 1001, + "handleStart": 0, + "handleEnd": 0 +} diff --git a/pype/configurations/defaults/presets/premiere/rules_tasks.json b/pype/configurations/defaults/presets/premiere/rules_tasks.json new file mode 100644 index 0000000000..333c9cd70b --- /dev/null +++ b/pype/configurations/defaults/presets/premiere/rules_tasks.json @@ -0,0 +1,21 @@ +{ + "defaultTasks": ["Layout", "Animation"], + "taskToSubsets": { + "Layout": ["reference", "audio"], + "Animation": ["audio"] + }, + "subsetToRepresentations": { + "reference": { + "preset": "h264", + "representation": "mp4" + }, + "thumbnail": { + "preset": "jpeg_thumb", + "representation": "jpg" + }, + "audio": { + "preset": "48khz", + "representation": "wav" + } + } +} diff --git a/pype/configurations/defaults/presets/standalone_publish/families.json b/pype/configurations/defaults/presets/standalone_publish/families.json new file mode 100644 index 0000000000..d05941cc26 --- /dev/null +++ b/pype/configurations/defaults/presets/standalone_publish/families.json @@ -0,0 +1,90 @@ +{ + "create_look": { + "name": "look", + "label": "Look", + "family": "look", + "icon": "paint-brush", + "defaults": ["Main"], + "help": "Shader connections defining shape look" + }, + "create_model": { + "name": "model", + "label": "Model", + "family": "model", + "icon": "cube", + "defaults": ["Main", "Proxy", "Sculpt"], + "help": "Polygonal static geometry" + }, + "create_workfile": { + "name": "workfile", + "label": "Workfile", + "family": "workfile", + "icon": "cube", + "defaults": ["Main"], + "help": "Working scene backup" + }, + "create_camera": { + "name": "camera", + "label": "Camera", + "family": "camera", + "icon": "video-camera", + "defaults": ["Main"], + "help": "Single baked camera" + }, + "create_pointcache": { + "name": "pointcache", + "label": "Pointcache", + "family": "pointcache", + "icon": "gears", + "defaults": ["Main"], + "help": "Alembic pointcache for animated data" + }, + "create_rig": { + "name": "rig", + "label": "Rig", + "family": "rig", + "icon": "wheelchair", + "defaults": ["Main"], + "help": "Artist-friendly rig with controls" + }, + "create_layout": { + "name": "layout", + "label": "Layout", + "family": "layout", + "icon": "cubes", + "defaults": ["Main"], + "help": "Simple scene for animators with camera" + }, + "create_plate": { + "name": "plate", + "label": "Plate", + "family": "plate", + "icon": "camera", + "defaults": ["Main", "BG", "Reference"], + "help": "Plates for compositors" + }, + "create_matchmove": { + "name": "matchmove", + "label": "Matchmove script", + "family": "matchmove", + "icon": "empire", + "defaults": ["Camera", "Object", "Mocap"], + "help": "Script exported from matchmoving application" + }, + "create_images": { + "name": "image", + "label": "Image file", + "family": "image", + "icon": "image", + "defaults": ["ConceptArt", "Reference", "Texture", "MattePaint"], + "help": "Holder for all kinds of image data" + }, + "create_editorial": { + "name": "editorial", + "label": "Editorial", + "family": "editorial", + "icon": "image", + "defaults": ["Main"], + "help": "Editorial files to generate shots." + } +} diff --git a/pype/configurations/defaults/presets/tools/creator.json b/pype/configurations/defaults/presets/tools/creator.json new file mode 100644 index 0000000000..d14e779f01 --- /dev/null +++ b/pype/configurations/defaults/presets/tools/creator.json @@ -0,0 +1,8 @@ +{ + "Model": ["model"], + "Render Globals": ["light", "render"], + "Layout": ["layout"], + "Set Dress": ["setdress"], + "Look": ["look"], + "Rig": ["rigging"] +} diff --git a/pype/configurations/defaults/presets/tools/project_folder_structure.json b/pype/configurations/defaults/presets/tools/project_folder_structure.json new file mode 100644 index 0000000000..83bd5f12a9 --- /dev/null +++ b/pype/configurations/defaults/presets/tools/project_folder_structure.json @@ -0,0 +1,22 @@ +{ + "__project_root__": { + "prod" : {}, + "resources" : { + "footage": { + "plates": {}, + "offline": {} + }, + "audio": {}, + "art_dept": {} + }, + "editorial" : {}, + "assets[ftrack.Library]": { + "characters[ftrack]": {}, + "locations[ftrack]": {} + }, + "shots[ftrack.Sequence]": { + "scripts": {}, + "editorial[ftrack.Folder]": {} + } + } +} diff --git a/pype/configurations/defaults/presets/tools/pyblish.json b/pype/configurations/defaults/presets/tools/pyblish.json new file mode 100644 index 0000000000..e81932ec45 --- /dev/null +++ b/pype/configurations/defaults/presets/tools/pyblish.json @@ -0,0 +1,17 @@ +{ + "ui": { + "intents": { + "__description__": [ + "In items you can specify {label: value} of intents", + "`default` may be used for setting default value." + ], + "default": "wip", + "items": { + "": "", + "wip": "WIP", + "test": "TEST", + "final": "FINAL" + } + } + } +} diff --git a/pype/configurations/defaults/presets/tools/slates/example_HD.json b/pype/configurations/defaults/presets/tools/slates/example_HD.json new file mode 100644 index 0000000000..b06391fb63 --- /dev/null +++ b/pype/configurations/defaults/presets/tools/slates/example_HD.json @@ -0,0 +1,212 @@ +{ + "width": 1920, + "height": 1080, + "destination_path": "{destination_path}", + "style": { + "*": { + "font-family": "arial", + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top" + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": true + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "font-bold": true, + "ellide": false, + "word-wrap": true, + "max-lines": null + }, + "table-item-col[1]": { + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" + } + }, + "items": [{ + "type": "layer", + "direction": 1, + "name": "MainLayer", + "style": { + "#MainLayer": { + "width": 1094, + "height": 1000, + "margin": 25, + "padding": 0 + }, + "#LeftSide": { + "margin-right": 25 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "items": [{ + "type": "layer", + "direction": 1, + "style": { + "table-item": { + "bg-color": "transparent", + "padding-bottom": 20 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": true, + "font-size": 40 + } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "{project[name]}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[0:1]": { + "width": 580 + } + } + }, { + "type": "table", + "values": [ + ["Submitting For:", "{intent}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[0:1]": { + "width": 218, + "alignment-horizontal": "right" + } + } + }] + }, { + "type": "rectangle", + "style": { + "bg-color": "#bc1015", + "width": 1108, + "height": 5, + "fill": true + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Version name:", "{version_name}"], + ["Date:", "{date}"], + ["Shot Types:", "{shot_type}"], + ["Submission Note:", "{submission_note}"] + ], + "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[0:1]": { + "font-bold": true + }, + "table-item-field[3:0]": { + "word-wrap": true, + "ellide": true, + "max-lines": 4 + }, + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 150 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 958 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "items": [{ + "type": "placeholder", + "name": "thumbnail", + "path": "{thumbnail_path}", + "style": { + "width": 730, + "height": 412 + } + }, { + "type": "placeholder", + "name": "colorbar", + "path": "{color_bar_path}", + "return_data": true, + "style": { + "width": 730, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Vendor:", "{vendor}"], + ["Shot Name:", "{shot_name}"], + ["Frames:", "{frame_start} - {frame_end} ({duration})"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left", + "width": 200 + }, + "table-item-col[1]": { + "alignment-horizontal": "right", + "width": 530, + "font-size": 30 + } + } + }] + }] + }] +} diff --git a/pype/configurations/defaults/presets/tools/sw_folders.json b/pype/configurations/defaults/presets/tools/sw_folders.json new file mode 100644 index 0000000000..a154935dce --- /dev/null +++ b/pype/configurations/defaults/presets/tools/sw_folders.json @@ -0,0 +1,8 @@ +{ + "compositing": ["nuke", "ae"], + "modeling": ["maya", "app2"], + "lookdev": ["substance"], + "animation": [], + "lighting": [], + "rigging": [] +} diff --git a/pype/configurations/defaults/presets/tools/workfiles.json b/pype/configurations/defaults/presets/tools/workfiles.json new file mode 100644 index 0000000000..393b2e3c10 --- /dev/null +++ b/pype/configurations/defaults/presets/tools/workfiles.json @@ -0,0 +1,7 @@ +{ + "last_workfile_on_startup": [ + { + "enabled": false + } + ] +} diff --git a/pype/configurations/defaults/presets/tray/menu_items.json b/pype/configurations/defaults/presets/tray/menu_items.json new file mode 100644 index 0000000000..6c6763848b --- /dev/null +++ b/pype/configurations/defaults/presets/tray/menu_items.json @@ -0,0 +1,28 @@ +{ + "item_usage": { + "User settings": false, + "Ftrack": true, + "Muster": false, + "Avalon": true, + "Clockify": false, + "Standalone Publish": true, + "Logging": true, + "Idle Manager": true, + "Timers Manager": true, + "Rest Api": true, + "Adobe Communicator": true + }, + "attributes": { + "Rest Api": { + "default_port": 8021, + "exclude_ports": [] + }, + "Timers Manager": { + "full_time": 15, + "message_time": 0.5 + }, + "Clockify": { + "workspace_name": null + } + } +} diff --git a/pype/configurations/defaults/presets/unreal/project_setup.json b/pype/configurations/defaults/presets/unreal/project_setup.json new file mode 100644 index 0000000000..8a4dffc526 --- /dev/null +++ b/pype/configurations/defaults/presets/unreal/project_setup.json @@ -0,0 +1,4 @@ +{ + "dev_mode": false, + "install_unreal_python_engine": false +} diff --git a/pype/configurations/defaults/project_configurations/plugins/celaction/publish.json b/pype/configurations/defaults/project_configurations/plugins/celaction/publish.json new file mode 100644 index 0000000000..fd1af23d84 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/celaction/publish.json @@ -0,0 +1,11 @@ +{ + "ExtractCelactionDeadline": { + "enabled": true, + "deadline_department": "", + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_group": "", + "deadline_chunk_size": 10 + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/config.json b/pype/configurations/defaults/project_configurations/plugins/config.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json b/pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json new file mode 100644 index 0000000000..d8d93a36ee --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json @@ -0,0 +1,7 @@ +{ + "IntegrateFtrackNote": { + "enabled": false, + "note_with_intent_template": "{intent}: {comment}", + "note_labels": [] + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/global/create.json b/pype/configurations/defaults/project_configurations/plugins/global/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/global/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/project_configurations/plugins/global/filter.json b/pype/configurations/defaults/project_configurations/plugins/global/filter.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/global/filter.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/project_configurations/plugins/global/load.json b/pype/configurations/defaults/project_configurations/plugins/global/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/global/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/project_configurations/plugins/global/publish.json b/pype/configurations/defaults/project_configurations/plugins/global/publish.json new file mode 100644 index 0000000000..3c5de85e68 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/global/publish.json @@ -0,0 +1,97 @@ +{ + "IntegrateMasterVersion": { + "enabled": false + }, + "ExtractJpegEXR": { + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "output": [] + } + }, + "ExtractReview": { + "enabled": true, + "profiles": [ + { + "families": [], + "hosts": [], + "outputs": { + "h264": { + "filter": { + "families": [ + "render", + "review", + "ftrack" + ] + }, + "ext": "mp4", + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "video_filters": [], + "audio_filters": [], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + }, + "tags": [ + "burnin", + "ftrackreview" + ] + } + } + } + ] + }, + "ExtractBurnin": { + "enabled": false, + "options": { + "font_size": 42, + "opacity": 1, + "bg_opacity": 0, + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5 + }, + "fields": {}, + "profiles": [ + { + "burnins": { + "burnin": { + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_RIGHT": "{anatomy[version]}", + "TOP_CENTERED": "", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "BOTTOM_CENTERED": "{asset}", + "BOTTOM_LEFT": "{username}" + } + } + } + ] + }, + "IntegrateAssetNew": { + "template_name_profiles": { + "publish": { + "families": [], + "tasks": [] + }, + "render": { + "families": [ + "review", + "render", + "prerender" + ] + } + } + }, + "ProcessSubmittedJobOnFarm": { + "enabled": false, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "" + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/create.json b/pype/configurations/defaults/project_configurations/plugins/maya/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/maya/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/filter.json b/pype/configurations/defaults/project_configurations/plugins/maya/filter.json new file mode 100644 index 0000000000..83d6f05f31 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/maya/filter.json @@ -0,0 +1,9 @@ +{ + "Preset n1": { + "ValidateNoAnimation": false, + "ValidateShapeDefaultNames": false + }, + "Preset n2": { + "ValidateNoAnimation": false + } +} diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/load.json b/pype/configurations/defaults/project_configurations/plugins/maya/load.json new file mode 100644 index 0000000000..260fbb35ee --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/maya/load.json @@ -0,0 +1,18 @@ +{ + "colors": { + "model": [0.821, 0.518, 0.117], + "rig": [0.144, 0.443, 0.463], + "pointcache": [0.368, 0.821, 0.117], + "animation": [0.368, 0.821, 0.117], + "ass": [1.0, 0.332, 0.312], + "camera": [0.447, 0.312, 1.0], + "fbx": [1.0, 0.931, 0.312], + "mayaAscii": [0.312, 1.0, 0.747], + "setdress": [0.312, 1.0, 0.747], + "layout": [0.312, 1.0, 0.747], + "vdbcache": [0.312, 1.0, 0.428], + "vrayproxy": [0.258, 0.95, 0.541], + "yeticache": [0.2, 0.8, 0.3], + "yetiRig": [0, 0.8, 0.5] + } +} diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/publish.json b/pype/configurations/defaults/project_configurations/plugins/maya/publish.json new file mode 100644 index 0000000000..2b3637ff80 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/maya/publish.json @@ -0,0 +1,17 @@ +{ + "ValidateModelName": { + "enabled": false, + "material_file": "/path/to/shader_name_definition.txt", + "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" + }, + "ValidateAssemblyName": { + "enabled": false + }, + "ValidateShaderName": { + "enabled": false, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": false + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json b/pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json new file mode 100644 index 0000000000..443bc2cb2c --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json @@ -0,0 +1,136 @@ +[ + { + "tasks": [ + "lighting" + ], + "current_context": [ + { + "subset_name_filters": [ + ".+[Mm]ain" + ], + "families": [ + "model" + ], + "repre_names": [ + "abc", + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "animation", + "pointcache" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "rendersetup" + ], + "repre_names": [ + "json" + ], + "loaders": [ + "RenderSetupLoader" + ] + }, + { + "families": [ + "camera" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + } + ], + "linked_assets": [ + { + "families": [ + "setdress" + ], + "repre_names": [ + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "ass" + ], + "repre_names": [ + "ass" + ], + "loaders": [ + "assLoader" + ] + } + ] + }, + { + "tasks": [ + "animation" + ], + "current_context": [ + { + "families": [ + "camera" + ], + "repre_names": [ + "abc", + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "audio" + ], + "repre_names": [ + "wav" + ], + "loaders": [ + "RenderSetupLoader" + ] + } + ], + "linked_assets": [ + { + "families": [ + "setdress" + ], + "repre_names": [ + "proxy" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "rig" + ], + "repre_names": [ + "ass" + ], + "loaders": [ + "rigLoader" + ] + } + ] + } +] \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/create.json b/pype/configurations/defaults/project_configurations/plugins/nuke/create.json new file mode 100644 index 0000000000..79ab665696 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/nuke/create.json @@ -0,0 +1,8 @@ +{ + "CreateWriteRender": { + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + }, + "CreateWritePrerender": { + "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/load.json b/pype/configurations/defaults/project_configurations/plugins/nuke/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/nuke/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/publish.json b/pype/configurations/defaults/project_configurations/plugins/nuke/publish.json new file mode 100644 index 0000000000..08a099a0a0 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/nuke/publish.json @@ -0,0 +1,53 @@ +{ + "ExtractThumbnail": { + "enabled": true, + "nodes": { + "Reformat": [ + [ + "type", + "to format" + ], + [ + "format", + "HD_1080" + ], + [ + "filter", + "Lanczos6" + ], + [ + "black_outside", + true + ], + [ + "pbb", + false + ] + ] + } + }, + "ValidateNukeWriteKnobs": { + "enabled": false, + "knobs": { + "render": { + "review": true + } + } + }, + "ExtractReviewDataLut": { + "enabled": false + }, + "ExtractReviewDataMov": { + "enabled": true, + "viewer_lut_raw": false + }, + "ExtractSlateFrame": { + "viewer_lut_raw": false + }, + "NukeSubmitDeadline": { + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_chunk_size": 1 + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json b/pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json new file mode 100644 index 0000000000..4b48b46184 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json @@ -0,0 +1,23 @@ +[ + { + "tasks": [ + "compositing" + ], + "current_context": [ + { + "families": [ + "render", + "plate" + ], + "repre_names": [ + "exr", + "dpx" + ], + "loaders": [ + "LoadSequence" + ] + } + ], + "linked_assets": [] + } +] \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json b/pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json new file mode 100644 index 0000000000..bd6a0dc1bd --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json @@ -0,0 +1,10 @@ +{ + "strict": { + "ValidateVersion": true, + "VersionUpWorkfile": true + }, + "benevolent": { + "ValidateVersion": false, + "VersionUpWorkfile": false + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json b/pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json new file mode 100644 index 0000000000..d99a878c35 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json @@ -0,0 +1,9 @@ +{ + "CollectInstanceVersion": { + "enabled": false + }, + "ExtractReviewCutUpVideo": { + "enabled": true, + "tags_addition": [] + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/resolve/create.json b/pype/configurations/defaults/project_configurations/plugins/resolve/create.json new file mode 100644 index 0000000000..8ff5b15714 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/resolve/create.json @@ -0,0 +1,7 @@ +{ + "CreateShotClip": { + "clipName": "{track}{sequence}{shot}", + "folder": "takes", + "steps": 20 + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json b/pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json new file mode 100644 index 0000000000..2f1a3e7aca --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json @@ -0,0 +1,27 @@ +{ + "ExtractThumbnailSP": { + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "output": [] + } + }, + "ExtractReviewSP": { + "outputs": { + "h264": { + "input": [ + "-gamma 2.2" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18" + ], + "tags": [ + "preview" + ], + "ext": "mov" + } + } + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/test/create.json b/pype/configurations/defaults/project_configurations/plugins/test/create.json new file mode 100644 index 0000000000..fa0b2fc05f --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/test/create.json @@ -0,0 +1,8 @@ +{ + "MyTestCreator": { + "my_test_property": "B", + "active": false, + "new_property": "new", + "family": "new_family" + } +} diff --git a/pype/configurations/defaults/project_configurations/plugins/test/publish.json b/pype/configurations/defaults/project_configurations/plugins/test/publish.json new file mode 100644 index 0000000000..3180dd5d8a --- /dev/null +++ b/pype/configurations/defaults/project_configurations/plugins/test/publish.json @@ -0,0 +1,10 @@ +{ + "MyTestPlugin": { + "label": "loaded from preset", + "optional": true, + "families": ["changed", "by", "preset"] + }, + "MyTestRemovedPlugin": { + "enabled": false + } +} diff --git a/pype/configurations/defaults/studio_configurations/global/applications.json b/pype/configurations/defaults/studio_configurations/global/applications.json new file mode 100644 index 0000000000..8e27f11002 --- /dev/null +++ b/pype/configurations/defaults/studio_configurations/global/applications.json @@ -0,0 +1,39 @@ +{ + "blender_2.80": true, + "blender_2.81": true, + "blender_2.82": true, + "blender_2.83": true, + "celaction_local": true, + "celaction_remote": true, + "harmony_17": true, + "houdini_16": true, + "houdini_17": true, + "houdini_18": true, + "maya_2016": true, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nukestudio_10.0": true, + "nukestudio_11.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "nukex_10.0": true, + "nukex_11.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nuke_10.0": true, + "nuke_11.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": true, + "photoshop_2020": true, + "premiere_2019": true, + "premiere_2020": true, + "resolve_16": true, + "storyboardpro_7": true, + "unreal_4.24": true, + "houdini_16.5": false +} \ No newline at end of file diff --git a/pype/configurations/defaults/studio_configurations/global/intent.json b/pype/configurations/defaults/studio_configurations/global/intent.json new file mode 100644 index 0000000000..844bd1b518 --- /dev/null +++ b/pype/configurations/defaults/studio_configurations/global/intent.json @@ -0,0 +1,8 @@ +{ + "items": { + "wip": "WIP", + "test": "TEST", + "final": "FINAL" + }, + "default": "wip" +} \ No newline at end of file diff --git a/pype/configurations/defaults/studio_configurations/global/tools.json b/pype/configurations/defaults/studio_configurations/global/tools.json new file mode 100644 index 0000000000..93895c0e81 --- /dev/null +++ b/pype/configurations/defaults/studio_configurations/global/tools.json @@ -0,0 +1,6 @@ +{ + "mtoa_3.0.1": true, + "mtoa_3.1.1": true, + "mtoa_3.2.0": true, + "yeti_2.1.2": true +} \ No newline at end of file diff --git a/pype/configurations/defaults/studio_configurations/global/tray_modules.json b/pype/configurations/defaults/studio_configurations/global/tray_modules.json new file mode 100644 index 0000000000..0ff5b15552 --- /dev/null +++ b/pype/configurations/defaults/studio_configurations/global/tray_modules.json @@ -0,0 +1,28 @@ +{ + "item_usage": { + "User settings": false, + "Ftrack": true, + "Muster": false, + "Avalon": true, + "Clockify": false, + "Standalone Publish": true, + "Logging": true, + "Idle Manager": true, + "Timers Manager": true, + "Rest Api": true, + "Adobe Communicator": true + }, + "attributes": { + "Rest Api": { + "default_port": 8021, + "exclude_ports": [] + }, + "Timers Manager": { + "full_time": 15.0, + "message_time": 0.5 + }, + "Clockify": { + "workspace_name": "" + } + } +} \ No newline at end of file diff --git a/pype/configurations/defaults/studio_configurations/muster/templates_mapping.json b/pype/configurations/defaults/studio_configurations/muster/templates_mapping.json new file mode 100644 index 0000000000..0c09113515 --- /dev/null +++ b/pype/configurations/defaults/studio_configurations/muster/templates_mapping.json @@ -0,0 +1,19 @@ +{ + "3delight": 41, + "arnold": 46, + "arnold_sf": 57, + "gelato": 30, + "harware": 3, + "krakatoa": 51, + "file_layers": 7, + "mentalray": 2, + "mentalray_sf": 6, + "redshift": 55, + "renderman": 29, + "software": 1, + "software_sf": 5, + "turtle": 10, + "vector": 4, + "vray": 37, + "ffmpeg": 48 +} \ No newline at end of file diff --git a/pype/configurations/defaults/studio_configurations/standalone_publish/families.json b/pype/configurations/defaults/studio_configurations/standalone_publish/families.json new file mode 100644 index 0000000000..d05941cc26 --- /dev/null +++ b/pype/configurations/defaults/studio_configurations/standalone_publish/families.json @@ -0,0 +1,90 @@ +{ + "create_look": { + "name": "look", + "label": "Look", + "family": "look", + "icon": "paint-brush", + "defaults": ["Main"], + "help": "Shader connections defining shape look" + }, + "create_model": { + "name": "model", + "label": "Model", + "family": "model", + "icon": "cube", + "defaults": ["Main", "Proxy", "Sculpt"], + "help": "Polygonal static geometry" + }, + "create_workfile": { + "name": "workfile", + "label": "Workfile", + "family": "workfile", + "icon": "cube", + "defaults": ["Main"], + "help": "Working scene backup" + }, + "create_camera": { + "name": "camera", + "label": "Camera", + "family": "camera", + "icon": "video-camera", + "defaults": ["Main"], + "help": "Single baked camera" + }, + "create_pointcache": { + "name": "pointcache", + "label": "Pointcache", + "family": "pointcache", + "icon": "gears", + "defaults": ["Main"], + "help": "Alembic pointcache for animated data" + }, + "create_rig": { + "name": "rig", + "label": "Rig", + "family": "rig", + "icon": "wheelchair", + "defaults": ["Main"], + "help": "Artist-friendly rig with controls" + }, + "create_layout": { + "name": "layout", + "label": "Layout", + "family": "layout", + "icon": "cubes", + "defaults": ["Main"], + "help": "Simple scene for animators with camera" + }, + "create_plate": { + "name": "plate", + "label": "Plate", + "family": "plate", + "icon": "camera", + "defaults": ["Main", "BG", "Reference"], + "help": "Plates for compositors" + }, + "create_matchmove": { + "name": "matchmove", + "label": "Matchmove script", + "family": "matchmove", + "icon": "empire", + "defaults": ["Camera", "Object", "Mocap"], + "help": "Script exported from matchmoving application" + }, + "create_images": { + "name": "image", + "label": "Image file", + "family": "image", + "icon": "image", + "defaults": ["ConceptArt", "Reference", "Texture", "MattePaint"], + "help": "Holder for all kinds of image data" + }, + "create_editorial": { + "name": "editorial", + "label": "Editorial", + "family": "editorial", + "icon": "image", + "defaults": ["Main"], + "help": "Editorial files to generate shots." + } +} From 3e6cf0cdb8c329de03ef92b58cb6f467a75a8ec0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:34:14 +0200 Subject: [PATCH 779/947] basic reorganization of presets --- .../configurations/defaults/presets/init.json | 4 - .../presets/plugins/celaction/publish.json | 10 --- .../defaults/presets/plugins/config.json | 1 - .../presets/plugins/ftrack/publish.json | 6 -- .../presets/plugins/global/create.json | 1 - .../presets/plugins/global/filter.json | 1 - .../defaults/presets/plugins/global/load.json | 1 - .../presets/plugins/global/publish.json | 86 ------------------- .../defaults/presets/plugins/maya/create.json | 1 - .../defaults/presets/plugins/maya/filter.json | 9 -- .../defaults/presets/plugins/maya/load.json | 18 ---- .../presets/plugins/maya/publish.json | 17 ---- .../presets/plugins/maya/workfile_build.json | 54 ------------ .../defaults/presets/plugins/nuke/create.json | 8 -- .../defaults/presets/plugins/nuke/load.json | 1 - .../presets/plugins/nuke/publish.json | 48 ----------- .../presets/plugins/nuke/workfile_build.json | 11 --- .../presets/plugins/nukestudio/filter.json | 10 --- .../presets/plugins/nukestudio/publish.json | 8 -- .../presets/plugins/resolve/create.json | 7 -- .../plugins/standalonepublisher/publish.json | 25 ------ .../defaults/presets/plugins/test/create.json | 8 -- .../presets/plugins/test/publish.json | 10 --- .../defaults/presets/tools/pyblish.json | 17 ---- .../defaults/presets/tray/menu_items.json | 28 ------ .../anatomy/README.md | 0 .../anatomy/colorspace.json} | 0 .../anatomy}/colorspace/aces103-cg.json | 0 .../anatomy/dataflow.json} | 0 .../anatomy}/dataflow/aces-exr.json | 0 .../anatomy/default.yaml | 0 .../anatomy/roots.json | 0 .../anatomy/templates.json | 35 ++++++++ .../{plugins => }/celaction/publish.json | 0 .../{plugins => }/config.json | 0 .../ftrack/ftrack_config.json | 0 .../ftrack/ftrack_custom_attributes.json | 0 .../ftrack/partnership_ftrack_cred.json | 0 .../ftrack/plugins/server.json | 0 .../ftrack/plugins/user.json | 0 .../ftrack/project_defaults.json | 0 .../{plugins => }/ftrack/publish.json | 0 .../{plugins/maya => global}/create.json | 0 .../global}/creator.json | 0 .../{plugins => }/global/filter.json | 0 .../{plugins/nuke => global}/load.json | 0 .../global}/project_folder_structure.json | 0 .../{plugins => }/global/publish.json | 0 .../global}/sw_folders.json | 0 .../global}/workfiles.json | 0 .../maya/capture.json | 0 .../{plugins/global => maya}/create.json | 0 .../{plugins => }/maya/filter.json | 0 .../{plugins => }/maya/load.json | 0 .../{plugins => }/maya/publish.json | 0 .../{plugins => }/maya/workfile_build.json | 0 .../muster/templates_mapping.json | 0 .../{plugins => }/nuke/create.json | 0 .../{plugins/global => nuke}/load.json | 0 .../{plugins => }/nuke/publish.json | 0 .../{plugins => }/nuke/workfile_build.json | 0 .../{plugins => }/nukestudio/filter.json | 0 .../{plugins => }/nukestudio/publish.json | 0 .../nukestudio/tags.json | 0 .../premiere/asset_default.json | 0 .../premiere/rules_tasks.json | 0 .../{plugins => }/resolve/create.json | 0 .../standalonepublisher}/families.json | 0 .../standalonepublisher/publish.json | 0 .../{plugins => }/test/create.json | 0 .../{plugins => }/test/publish.json | 0 .../tools/slates/example_HD.json | 0 .../unreal/project_setup.json | 0 .../environments/avalon.json | 0 .../environments/blender.json | 0 .../environments/celaction.json | 0 .../environments/deadline.json | 0 .../environments/ftrack.json | 0 .../environments/global.json | 0 .../environments/harmony.json | 0 .../environments/houdini.json | 0 .../environments/maya.json | 0 .../environments/maya_2018.json | 0 .../environments/maya_2020.json | 0 .../environments/mayabatch.json | 0 .../environments/mayabatch_2019.json | 0 .../environments/mtoa_3.1.1.json | 0 .../environments/muster.json | 0 .../environments/nuke.json | 0 .../environments/nukestudio.json | 0 .../environments/nukestudio_10.0.json | 0 .../environments/nukex.json | 0 .../environments/nukex_10.0.json | 0 .../environments/photoshop.json | 0 .../environments/premiere.json | 0 .../environments/resolve.json | 0 .../environments/storyboardpro.json | 0 .../environments/unreal_4.24.json | 0 .../environments/vray_4300.json | 0 .../launchers/blender_2.80.toml | 0 .../launchers/blender_2.81.toml | 0 .../launchers/blender_2.82.toml | 0 .../launchers/blender_2.83.toml | 0 .../launchers/celaction_local.toml | 0 .../launchers/celaction_publish.toml | 0 .../launchers/darwin/blender_2.82 | 0 .../launchers/darwin/harmony_17 | 0 .../launchers/darwin/harmony_17_launch | 0 .../launchers/darwin/python3 | 0 .../launchers/harmony_17.toml | 0 .../launchers/houdini_16.toml | 0 .../launchers/houdini_17.toml | 0 .../launchers/houdini_18.toml | 0 .../launchers/linux/maya2016 | 0 .../launchers/linux/maya2017 | 0 .../launchers/linux/maya2018 | 0 .../launchers/linux/maya2019 | 0 .../launchers/linux/maya2020 | 0 .../launchers/linux/nuke11.3 | 0 .../launchers/linux/nuke12.0 | 0 .../launchers/linux/nukestudio11.3 | 0 .../launchers/linux/nukestudio12.0 | 0 .../launchers/linux/nukex11.3 | 0 .../launchers/linux/nukex12.0 | 0 .../launchers/maya_2016.toml | 0 .../launchers/maya_2017.toml | 0 .../launchers/maya_2018.toml | 0 .../launchers/maya_2019.toml | 0 .../launchers/maya_2020.toml | 0 .../launchers/mayabatch_2019.toml | 0 .../launchers/mayabatch_2020.toml | 0 .../launchers/mayapy2016.toml | 0 .../launchers/mayapy2017.toml | 0 .../launchers/mayapy2018.toml | 0 .../launchers/mayapy2019.toml | 0 .../launchers/mayapy2020.toml | 0 .../launchers/myapp.toml | 0 .../launchers/nuke_10.0.toml | 0 .../launchers/nuke_11.0.toml | 0 .../launchers/nuke_11.2.toml | 0 .../launchers/nuke_11.3.toml | 0 .../launchers/nuke_12.0.toml | 0 .../launchers/nukestudio_10.0.toml | 0 .../launchers/nukestudio_11.0.toml | 0 .../launchers/nukestudio_11.2.toml | 0 .../launchers/nukestudio_11.3.toml | 0 .../launchers/nukestudio_12.0.toml | 0 .../launchers/nukex_10.0.toml | 0 .../launchers/nukex_11.0.toml | 0 .../launchers/nukex_11.2.toml | 0 .../launchers/nukex_11.3.toml | 0 .../launchers/nukex_12.0.toml | 0 .../launchers/photoshop_2020.toml | 0 .../launchers/premiere_2019.toml | 0 .../launchers/premiere_2020.toml | 0 .../launchers/python_2.toml | 0 .../launchers/python_3.toml | 0 .../launchers/resolve_16.toml | 0 .../launchers/shell.toml | 0 .../launchers/storyboardpro_7.toml | 0 .../launchers/unreal_4.24.toml | 0 .../launchers/windows/blender_2.80.bat | 0 .../launchers/windows/blender_2.81.bat | 0 .../launchers/windows/blender_2.82.bat | 0 .../launchers/windows/blender_2.83.bat | 0 .../launchers/windows/celaction_local.bat | 0 .../launchers/windows/celaction_publish.bat | 0 .../launchers/windows/harmony_17.bat | 0 .../launchers/windows/houdini_16.bat | 0 .../launchers/windows/houdini_17.bat | 0 .../launchers/windows/houdini_18.bat | 0 .../launchers/windows/maya2016.bat | 0 .../launchers/windows/maya2017.bat | 0 .../launchers/windows/maya2018.bat | 0 .../launchers/windows/maya2019.bat | 0 .../launchers/windows/maya2020.bat | 0 .../launchers/windows/mayabatch2019.bat | 0 .../launchers/windows/mayabatch2020.bat | 0 .../launchers/windows/mayapy2016.bat | 0 .../launchers/windows/mayapy2017.bat | 0 .../launchers/windows/mayapy2018.bat | 0 .../launchers/windows/mayapy2019.bat | 0 .../launchers/windows/mayapy2020.bat | 0 .../launchers/windows/nuke10.0.bat | 0 .../launchers/windows/nuke11.0.bat | 0 .../launchers/windows/nuke11.2.bat | 0 .../launchers/windows/nuke11.3.bat | 0 .../launchers/windows/nuke12.0.bat | 0 .../launchers/windows/nukestudio10.0.bat | 0 .../launchers/windows/nukestudio11.0.bat | 0 .../launchers/windows/nukestudio11.2.bat | 0 .../launchers/windows/nukestudio11.3.bat | 0 .../launchers/windows/nukestudio12.0.bat | 0 .../launchers/windows/nukex10.0.bat | 0 .../launchers/windows/nukex11.0.bat | 0 .../launchers/windows/nukex11.2.bat | 0 .../launchers/windows/nukex11.3.bat | 0 .../launchers/windows/nukex12.0.bat | 0 .../launchers/windows/photoshop_2020.bat | 0 .../launchers/windows/premiere_pro_2019.bat | 0 .../launchers/windows/premiere_pro_2020.bat | 0 .../launchers/windows/python3.bat | 0 .../launchers/windows/resolve_16.bat | 0 .../launchers/windows/shell.bat | 0 .../launchers/windows/storyboardpro_7.bat | 0 .../launchers/windows/unreal.bat | 0 206 files changed, 35 insertions(+), 390 deletions(-) delete mode 100644 pype/configurations/defaults/presets/init.json delete mode 100644 pype/configurations/defaults/presets/plugins/celaction/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/config.json delete mode 100644 pype/configurations/defaults/presets/plugins/ftrack/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/global/create.json delete mode 100644 pype/configurations/defaults/presets/plugins/global/filter.json delete mode 100644 pype/configurations/defaults/presets/plugins/global/load.json delete mode 100644 pype/configurations/defaults/presets/plugins/global/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/maya/create.json delete mode 100644 pype/configurations/defaults/presets/plugins/maya/filter.json delete mode 100644 pype/configurations/defaults/presets/plugins/maya/load.json delete mode 100644 pype/configurations/defaults/presets/plugins/maya/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/maya/workfile_build.json delete mode 100644 pype/configurations/defaults/presets/plugins/nuke/create.json delete mode 100644 pype/configurations/defaults/presets/plugins/nuke/load.json delete mode 100644 pype/configurations/defaults/presets/plugins/nuke/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/nuke/workfile_build.json delete mode 100644 pype/configurations/defaults/presets/plugins/nukestudio/filter.json delete mode 100644 pype/configurations/defaults/presets/plugins/nukestudio/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/resolve/create.json delete mode 100644 pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json delete mode 100644 pype/configurations/defaults/presets/plugins/test/create.json delete mode 100644 pype/configurations/defaults/presets/plugins/test/publish.json delete mode 100644 pype/configurations/defaults/presets/tools/pyblish.json delete mode 100644 pype/configurations/defaults/presets/tray/menu_items.json rename pype/configurations/defaults/{ => project_configurations}/anatomy/README.md (100%) rename pype/configurations/defaults/{presets/colorspace/default.json => project_configurations/anatomy/colorspace.json} (100%) rename pype/configurations/defaults/{presets => project_configurations/anatomy}/colorspace/aces103-cg.json (100%) rename pype/configurations/defaults/{presets/dataflow/default.json => project_configurations/anatomy/dataflow.json} (100%) rename pype/configurations/defaults/{presets => project_configurations/anatomy}/dataflow/aces-exr.json (100%) rename pype/configurations/defaults/{ => project_configurations}/anatomy/default.yaml (100%) rename pype/configurations/defaults/{ => project_configurations}/anatomy/roots.json (100%) create mode 100644 pype/configurations/defaults/project_configurations/anatomy/templates.json rename pype/configurations/defaults/project_configurations/{plugins => }/celaction/publish.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/config.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/ftrack/ftrack_config.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/ftrack/ftrack_custom_attributes.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/ftrack/partnership_ftrack_cred.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/ftrack/plugins/server.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/ftrack/plugins/user.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/ftrack/project_defaults.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/ftrack/publish.json (100%) rename pype/configurations/defaults/project_configurations/{plugins/maya => global}/create.json (100%) rename pype/configurations/defaults/{presets/tools => project_configurations/global}/creator.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/global/filter.json (100%) rename pype/configurations/defaults/project_configurations/{plugins/nuke => global}/load.json (100%) rename pype/configurations/defaults/{presets/tools => project_configurations/global}/project_folder_structure.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/global/publish.json (100%) rename pype/configurations/defaults/{presets/tools => project_configurations/global}/sw_folders.json (100%) rename pype/configurations/defaults/{presets/tools => project_configurations/global}/workfiles.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/maya/capture.json (100%) rename pype/configurations/defaults/project_configurations/{plugins/global => maya}/create.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/maya/filter.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/maya/load.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/maya/publish.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/maya/workfile_build.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/muster/templates_mapping.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/nuke/create.json (100%) rename pype/configurations/defaults/project_configurations/{plugins/global => nuke}/load.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/nuke/publish.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/nuke/workfile_build.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/nukestudio/filter.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/nukestudio/publish.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/nukestudio/tags.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/premiere/asset_default.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/premiere/rules_tasks.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/resolve/create.json (100%) rename pype/configurations/defaults/{presets/standalone_publish => project_configurations/standalonepublisher}/families.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/standalonepublisher/publish.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/test/create.json (100%) rename pype/configurations/defaults/project_configurations/{plugins => }/test/publish.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/tools/slates/example_HD.json (100%) rename pype/configurations/defaults/{presets => project_configurations}/unreal/project_setup.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/avalon.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/blender.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/celaction.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/deadline.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/ftrack.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/global.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/harmony.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/houdini.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/maya.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/maya_2018.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/maya_2020.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/mayabatch.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/mayabatch_2019.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/mtoa_3.1.1.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/muster.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/nuke.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/nukestudio.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/nukestudio_10.0.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/nukex.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/nukex_10.0.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/photoshop.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/premiere.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/resolve.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/storyboardpro.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/unreal_4.24.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/environments/vray_4300.json (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/blender_2.80.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/blender_2.81.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/blender_2.82.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/blender_2.83.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/celaction_local.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/celaction_publish.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/darwin/blender_2.82 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/darwin/harmony_17 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/darwin/harmony_17_launch (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/darwin/python3 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/harmony_17.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/houdini_16.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/houdini_17.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/houdini_18.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/maya2016 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/maya2017 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/maya2018 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/maya2019 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/maya2020 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/nuke11.3 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/nuke12.0 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/nukestudio11.3 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/nukestudio12.0 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/nukex11.3 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/linux/nukex12.0 (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/maya_2016.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/maya_2017.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/maya_2018.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/maya_2019.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/maya_2020.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayabatch_2019.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayabatch_2020.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayapy2016.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayapy2017.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayapy2018.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayapy2019.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/mayapy2020.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/myapp.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nuke_10.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nuke_11.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nuke_11.2.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nuke_11.3.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nuke_12.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukestudio_10.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukestudio_11.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukestudio_11.2.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukestudio_11.3.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukestudio_12.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukex_10.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukex_11.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukex_11.2.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukex_11.3.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/nukex_12.0.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/photoshop_2020.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/premiere_2019.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/premiere_2020.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/python_2.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/python_3.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/resolve_16.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/shell.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/storyboardpro_7.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/unreal_4.24.toml (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/blender_2.80.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/blender_2.81.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/blender_2.82.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/blender_2.83.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/celaction_local.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/celaction_publish.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/harmony_17.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/houdini_16.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/houdini_17.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/houdini_18.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/maya2016.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/maya2017.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/maya2018.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/maya2019.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/maya2020.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayabatch2019.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayabatch2020.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayapy2016.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayapy2017.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayapy2018.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayapy2019.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/mayapy2020.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nuke10.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nuke11.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nuke11.2.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nuke11.3.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nuke12.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukestudio10.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukestudio11.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukestudio11.2.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukestudio11.3.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukestudio12.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukex10.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukex11.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukex11.2.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukex11.3.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/nukex12.0.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/photoshop_2020.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/premiere_pro_2019.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/premiere_pro_2020.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/python3.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/resolve_16.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/shell.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/storyboardpro_7.bat (100%) rename pype/configurations/defaults/{ => studio_configurations}/launchers/windows/unreal.bat (100%) diff --git a/pype/configurations/defaults/presets/init.json b/pype/configurations/defaults/presets/init.json deleted file mode 100644 index 361ee7445b..0000000000 --- a/pype/configurations/defaults/presets/init.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "colorspace": "default", - "dataflow": "default" -} diff --git a/pype/configurations/defaults/presets/plugins/celaction/publish.json b/pype/configurations/defaults/presets/plugins/celaction/publish.json deleted file mode 100644 index e791f574d9..0000000000 --- a/pype/configurations/defaults/presets/plugins/celaction/publish.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "ExtractCelactionDeadline": { - "deadline_department": "", - "deadline_priority": 50, - "deadline_pool": "", - "deadline_pool_secondary": "", - "deadline_group": "", - "deadline_chunk_size": 10 - } -} diff --git a/pype/configurations/defaults/presets/plugins/config.json b/pype/configurations/defaults/presets/plugins/config.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/pype/configurations/defaults/presets/plugins/config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/pype/configurations/defaults/presets/plugins/ftrack/publish.json b/pype/configurations/defaults/presets/plugins/ftrack/publish.json deleted file mode 100644 index d0469ae4f7..0000000000 --- a/pype/configurations/defaults/presets/plugins/ftrack/publish.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "IntegrateFtrackNote": { - "note_with_intent_template": "{intent}: {comment}", - "note_labels": [] - } -} diff --git a/pype/configurations/defaults/presets/plugins/global/create.json b/pype/configurations/defaults/presets/plugins/global/create.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/configurations/defaults/presets/plugins/global/create.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/configurations/defaults/presets/plugins/global/filter.json b/pype/configurations/defaults/presets/plugins/global/filter.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/configurations/defaults/presets/plugins/global/filter.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/configurations/defaults/presets/plugins/global/load.json b/pype/configurations/defaults/presets/plugins/global/load.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/configurations/defaults/presets/plugins/global/load.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/configurations/defaults/presets/plugins/global/publish.json b/pype/configurations/defaults/presets/plugins/global/publish.json deleted file mode 100644 index 016868fc92..0000000000 --- a/pype/configurations/defaults/presets/plugins/global/publish.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "IntegrateMasterVersion": { - "enabled": false - }, - "ExtractJpegEXR": { - "ffmpeg_args": { - "input": [ - "-gamma 2.2" - ], - "output": [] - } - }, - "ExtractReview": { - "__documentation__": "http://pype.club/docs/admin_presets_plugins", - "profiles": [ - { - "families": [], - "hosts": [], - "outputs": { - "h264": { - "filter": { - "families": ["render", "review", "ftrack"] - }, - "ext": "mp4", - "ffmpeg_args": { - "input": [ - "-gamma 2.2" - ], - "video_filters": [], - "audio_filters": [], - "output": [ - "-pix_fmt yuv420p", - "-crf 18", - "-intra" - ] - }, - "tags": ["burnin", "ftrackreview"] - } - } - } - ] - }, - "ExtractBurnin": { - "options": { - "opacity": 1, - "x_offset": 5, - "y_offset": 5, - "bg_padding": 5, - "bg_opacity": 0.5, - "font_size": 42 - }, - "fields": { - - }, - "profiles": [ - { - "burnins": { - "burnin": { - "TOP_LEFT": "{yy}-{mm}-{dd}", - "TOP_RIGHT": "{anatomy[version]}", - "TOP_CENTERED": "", - "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", - "BOTTOM_CENTERED": "{asset}", - "BOTTOM_LEFT": "{username}" - } - } - } - ] - }, - "IntegrateAssetNew": { - "template_name_profiles": { - "publish": { - "families": [], - "tasks": [] - }, - "render": { - "families": ["review", "render", "prerender"] - } - } - }, - "ProcessSubmittedJobOnFarm": { - "deadline_department": "", - "deadline_pool": "", - "deadline_group": "" - } -} diff --git a/pype/configurations/defaults/presets/plugins/maya/create.json b/pype/configurations/defaults/presets/plugins/maya/create.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/configurations/defaults/presets/plugins/maya/create.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/configurations/defaults/presets/plugins/maya/filter.json b/pype/configurations/defaults/presets/plugins/maya/filter.json deleted file mode 100644 index 83d6f05f31..0000000000 --- a/pype/configurations/defaults/presets/plugins/maya/filter.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Preset n1": { - "ValidateNoAnimation": false, - "ValidateShapeDefaultNames": false - }, - "Preset n2": { - "ValidateNoAnimation": false - } -} diff --git a/pype/configurations/defaults/presets/plugins/maya/load.json b/pype/configurations/defaults/presets/plugins/maya/load.json deleted file mode 100644 index 260fbb35ee..0000000000 --- a/pype/configurations/defaults/presets/plugins/maya/load.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "colors": { - "model": [0.821, 0.518, 0.117], - "rig": [0.144, 0.443, 0.463], - "pointcache": [0.368, 0.821, 0.117], - "animation": [0.368, 0.821, 0.117], - "ass": [1.0, 0.332, 0.312], - "camera": [0.447, 0.312, 1.0], - "fbx": [1.0, 0.931, 0.312], - "mayaAscii": [0.312, 1.0, 0.747], - "setdress": [0.312, 1.0, 0.747], - "layout": [0.312, 1.0, 0.747], - "vdbcache": [0.312, 1.0, 0.428], - "vrayproxy": [0.258, 0.95, 0.541], - "yeticache": [0.2, 0.8, 0.3], - "yetiRig": [0, 0.8, 0.5] - } -} diff --git a/pype/configurations/defaults/presets/plugins/maya/publish.json b/pype/configurations/defaults/presets/plugins/maya/publish.json deleted file mode 100644 index 2e2b3164f3..0000000000 --- a/pype/configurations/defaults/presets/plugins/maya/publish.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ValidateModelName": { - "enabled": false, - "material_file": "/path/to/shader_name_definition.txt", - "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" - }, - "ValidateAssemblyName": { - "enabled": false - }, - "ValidateShaderName": { - "enabled": false, - "regex": "(?P.*)_(.*)_SHD" - }, - "ValidateMeshHasOverlappingUVs": { - "enabled": false - } -} diff --git a/pype/configurations/defaults/presets/plugins/maya/workfile_build.json b/pype/configurations/defaults/presets/plugins/maya/workfile_build.json deleted file mode 100644 index 2872b783cb..0000000000 --- a/pype/configurations/defaults/presets/plugins/maya/workfile_build.json +++ /dev/null @@ -1,54 +0,0 @@ -[{ - "tasks": ["lighting"], - - "current_context": [{ - "subset_name_filters": [".+[Mm]ain"], - "families": ["model"], - "repre_names": ["abc", "ma"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["animation", "pointcache"], - "repre_names": ["abc"], - "loaders": ["ReferenceLoader"] - },{ - "families": ["rendersetup"], - "repre_names": ["json"], - "loaders": ["RenderSetupLoader"] - }, { - "families": ["camera"], - "repre_names": ["abc"], - "loaders": ["ReferenceLoader"] - }], - - "linked_assets": [{ - "families": ["setdress"], - "repre_names": ["ma"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["ass"], - "repre_names": ["ass"], - "loaders":["assLoader"] - }] -}, { - "tasks": ["animation"], - - "current_context": [{ - "families": ["camera"], - "repre_names": ["abc", "ma"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["audio"], - "repre_names": ["wav"], - "loaders": ["RenderSetupLoader"] - }], - - "linked_assets": [{ - "families": ["setdress"], - "repre_names": ["proxy"], - "loaders": ["ReferenceLoader"] - }, { - "families": ["rig"], - "repre_names": ["ass"], - "loaders": ["rigLoader"] - }] -}] diff --git a/pype/configurations/defaults/presets/plugins/nuke/create.json b/pype/configurations/defaults/presets/plugins/nuke/create.json deleted file mode 100644 index 4deb0b4ad5..0000000000 --- a/pype/configurations/defaults/presets/plugins/nuke/create.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "CreateWriteRender": { - "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" - }, - "CreateWritePrerender": { - "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" - } -} diff --git a/pype/configurations/defaults/presets/plugins/nuke/load.json b/pype/configurations/defaults/presets/plugins/nuke/load.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/pype/configurations/defaults/presets/plugins/nuke/load.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/pype/configurations/defaults/presets/plugins/nuke/publish.json b/pype/configurations/defaults/presets/plugins/nuke/publish.json deleted file mode 100644 index ab0d0e76a5..0000000000 --- a/pype/configurations/defaults/presets/plugins/nuke/publish.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "ExtractThumbnail": { - "nodes": { - "Reformat": [ - ["type", "to format"], - ["format", "HD_1080"], - ["filter", "Lanczos6"], - ["black_outside", true], - ["pbb", false] - ] - } - }, - "ValidateNukeWriteKnobs": { - "enabled": false, - "knobs": { - "render": { - "review": true - } - } - }, - "ExtractReviewDataLut": { - "__documentation__": { - "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", - "enabled": "keep in on `false` if you want Nuke baking colorspace workflow applied else FFmpeg will convert imgsequence with baked LUT" - }, - "enabled": false - }, - "ExtractReviewDataMov": { - "__documentation__": { - "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", - "enabled": "keep in on `true` if you want Nuke baking colorspace workflow applied" - }, - "enabled": true, - "viewer_lut_raw": false - }, - "ExtractSlateFrame": { - "__documentation__": { - "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`" - }, - "viewer_lut_raw": false - }, - "NukeSubmitDeadline": { - "deadline_priority": 50, - "deadline_pool": "", - "deadline_pool_secondary": "", - "deadline_chunk_size": 1 - } -} diff --git a/pype/configurations/defaults/presets/plugins/nuke/workfile_build.json b/pype/configurations/defaults/presets/plugins/nuke/workfile_build.json deleted file mode 100644 index d3613c929e..0000000000 --- a/pype/configurations/defaults/presets/plugins/nuke/workfile_build.json +++ /dev/null @@ -1,11 +0,0 @@ -[{ - "tasks": ["compositing"], - - "current_context": [{ - "families": ["render", "plate"], - "repre_names": ["exr" ,"dpx"], - "loaders": ["LoadSequence"] - }], - - "linked_assets": [] -}] diff --git a/pype/configurations/defaults/presets/plugins/nukestudio/filter.json b/pype/configurations/defaults/presets/plugins/nukestudio/filter.json deleted file mode 100644 index bd6a0dc1bd..0000000000 --- a/pype/configurations/defaults/presets/plugins/nukestudio/filter.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "strict": { - "ValidateVersion": true, - "VersionUpWorkfile": true - }, - "benevolent": { - "ValidateVersion": false, - "VersionUpWorkfile": false - } -} \ No newline at end of file diff --git a/pype/configurations/defaults/presets/plugins/nukestudio/publish.json b/pype/configurations/defaults/presets/plugins/nukestudio/publish.json deleted file mode 100644 index 8c4ad133f1..0000000000 --- a/pype/configurations/defaults/presets/plugins/nukestudio/publish.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "CollectInstanceVersion": { - "enabled": false - }, - "ExtractReviewCutUpVideo": { - "tags_addition": [] - } -} diff --git a/pype/configurations/defaults/presets/plugins/resolve/create.json b/pype/configurations/defaults/presets/plugins/resolve/create.json deleted file mode 100644 index 29ca5900fb..0000000000 --- a/pype/configurations/defaults/presets/plugins/resolve/create.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "CreateShotClip": { - "clipName": "{track}{sequence}{shot}", - "folder": "takes", - "steps": 20 - } -} diff --git a/pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json b/pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json deleted file mode 100644 index 2b2fb660c2..0000000000 --- a/pype/configurations/defaults/presets/plugins/standalonepublisher/publish.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "ExtractThumbnailSP": { - "ffmpeg_args": { - "input": [ - "-gamma 2.2" - ], - "output": [] - } - }, - "ExtractReviewSP": { - "outputs": { - "h264": { - "input": [ - "-gamma 2.2" - ], - "output": [ - "-pix_fmt yuv420p", - "-crf 18" - ], - "tags": ["preview"], - "ext": "mov" - } - } - } -} diff --git a/pype/configurations/defaults/presets/plugins/test/create.json b/pype/configurations/defaults/presets/plugins/test/create.json deleted file mode 100644 index fa0b2fc05f..0000000000 --- a/pype/configurations/defaults/presets/plugins/test/create.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "MyTestCreator": { - "my_test_property": "B", - "active": false, - "new_property": "new", - "family": "new_family" - } -} diff --git a/pype/configurations/defaults/presets/plugins/test/publish.json b/pype/configurations/defaults/presets/plugins/test/publish.json deleted file mode 100644 index 3180dd5d8a..0000000000 --- a/pype/configurations/defaults/presets/plugins/test/publish.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "MyTestPlugin": { - "label": "loaded from preset", - "optional": true, - "families": ["changed", "by", "preset"] - }, - "MyTestRemovedPlugin": { - "enabled": false - } -} diff --git a/pype/configurations/defaults/presets/tools/pyblish.json b/pype/configurations/defaults/presets/tools/pyblish.json deleted file mode 100644 index e81932ec45..0000000000 --- a/pype/configurations/defaults/presets/tools/pyblish.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ui": { - "intents": { - "__description__": [ - "In items you can specify {label: value} of intents", - "`default` may be used for setting default value." - ], - "default": "wip", - "items": { - "": "", - "wip": "WIP", - "test": "TEST", - "final": "FINAL" - } - } - } -} diff --git a/pype/configurations/defaults/presets/tray/menu_items.json b/pype/configurations/defaults/presets/tray/menu_items.json deleted file mode 100644 index 6c6763848b..0000000000 --- a/pype/configurations/defaults/presets/tray/menu_items.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "item_usage": { - "User settings": false, - "Ftrack": true, - "Muster": false, - "Avalon": true, - "Clockify": false, - "Standalone Publish": true, - "Logging": true, - "Idle Manager": true, - "Timers Manager": true, - "Rest Api": true, - "Adobe Communicator": true - }, - "attributes": { - "Rest Api": { - "default_port": 8021, - "exclude_ports": [] - }, - "Timers Manager": { - "full_time": 15, - "message_time": 0.5 - }, - "Clockify": { - "workspace_name": null - } - } -} diff --git a/pype/configurations/defaults/anatomy/README.md b/pype/configurations/defaults/project_configurations/anatomy/README.md similarity index 100% rename from pype/configurations/defaults/anatomy/README.md rename to pype/configurations/defaults/project_configurations/anatomy/README.md diff --git a/pype/configurations/defaults/presets/colorspace/default.json b/pype/configurations/defaults/project_configurations/anatomy/colorspace.json similarity index 100% rename from pype/configurations/defaults/presets/colorspace/default.json rename to pype/configurations/defaults/project_configurations/anatomy/colorspace.json diff --git a/pype/configurations/defaults/presets/colorspace/aces103-cg.json b/pype/configurations/defaults/project_configurations/anatomy/colorspace/aces103-cg.json similarity index 100% rename from pype/configurations/defaults/presets/colorspace/aces103-cg.json rename to pype/configurations/defaults/project_configurations/anatomy/colorspace/aces103-cg.json diff --git a/pype/configurations/defaults/presets/dataflow/default.json b/pype/configurations/defaults/project_configurations/anatomy/dataflow.json similarity index 100% rename from pype/configurations/defaults/presets/dataflow/default.json rename to pype/configurations/defaults/project_configurations/anatomy/dataflow.json diff --git a/pype/configurations/defaults/presets/dataflow/aces-exr.json b/pype/configurations/defaults/project_configurations/anatomy/dataflow/aces-exr.json similarity index 100% rename from pype/configurations/defaults/presets/dataflow/aces-exr.json rename to pype/configurations/defaults/project_configurations/anatomy/dataflow/aces-exr.json diff --git a/pype/configurations/defaults/anatomy/default.yaml b/pype/configurations/defaults/project_configurations/anatomy/default.yaml similarity index 100% rename from pype/configurations/defaults/anatomy/default.yaml rename to pype/configurations/defaults/project_configurations/anatomy/default.yaml diff --git a/pype/configurations/defaults/anatomy/roots.json b/pype/configurations/defaults/project_configurations/anatomy/roots.json similarity index 100% rename from pype/configurations/defaults/anatomy/roots.json rename to pype/configurations/defaults/project_configurations/anatomy/roots.json diff --git a/pype/configurations/defaults/project_configurations/anatomy/templates.json b/pype/configurations/defaults/project_configurations/anatomy/templates.json new file mode 100644 index 0000000000..23e9f308f2 --- /dev/null +++ b/pype/configurations/defaults/project_configurations/anatomy/templates.json @@ -0,0 +1,35 @@ +{ + "version_padding": 3, + "version": "v{version:0>{@version_padding}}", + "frame_padding": 4, + "frame": "{frame:0>{@frame_padding}}", + + "work": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/work/{task}", + "file": "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}", + "path": "{@folder}/{@file}" + }, + + "render": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/render/{subset}/{@version}", + "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", + "path": "{@folder}/{@file}" + }, + + "texture": { + "path": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}" + }, + + "publish": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", + "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", + "path": "{@folder}/{@file}", + "thumbnail": "{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}{ext}" + }, + + "master": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/master", + "file": "{project[code]}_{asset}_{subset}_master<_{output}><.{frame}>.{representation}", + "path": "{@folder}/{@file}" + } +} diff --git a/pype/configurations/defaults/project_configurations/plugins/celaction/publish.json b/pype/configurations/defaults/project_configurations/celaction/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/celaction/publish.json rename to pype/configurations/defaults/project_configurations/celaction/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/config.json b/pype/configurations/defaults/project_configurations/config.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/config.json rename to pype/configurations/defaults/project_configurations/config.json diff --git a/pype/configurations/defaults/presets/ftrack/ftrack_config.json b/pype/configurations/defaults/project_configurations/ftrack/ftrack_config.json similarity index 100% rename from pype/configurations/defaults/presets/ftrack/ftrack_config.json rename to pype/configurations/defaults/project_configurations/ftrack/ftrack_config.json diff --git a/pype/configurations/defaults/presets/ftrack/ftrack_custom_attributes.json b/pype/configurations/defaults/project_configurations/ftrack/ftrack_custom_attributes.json similarity index 100% rename from pype/configurations/defaults/presets/ftrack/ftrack_custom_attributes.json rename to pype/configurations/defaults/project_configurations/ftrack/ftrack_custom_attributes.json diff --git a/pype/configurations/defaults/presets/ftrack/partnership_ftrack_cred.json b/pype/configurations/defaults/project_configurations/ftrack/partnership_ftrack_cred.json similarity index 100% rename from pype/configurations/defaults/presets/ftrack/partnership_ftrack_cred.json rename to pype/configurations/defaults/project_configurations/ftrack/partnership_ftrack_cred.json diff --git a/pype/configurations/defaults/presets/ftrack/plugins/server.json b/pype/configurations/defaults/project_configurations/ftrack/plugins/server.json similarity index 100% rename from pype/configurations/defaults/presets/ftrack/plugins/server.json rename to pype/configurations/defaults/project_configurations/ftrack/plugins/server.json diff --git a/pype/configurations/defaults/presets/ftrack/plugins/user.json b/pype/configurations/defaults/project_configurations/ftrack/plugins/user.json similarity index 100% rename from pype/configurations/defaults/presets/ftrack/plugins/user.json rename to pype/configurations/defaults/project_configurations/ftrack/plugins/user.json diff --git a/pype/configurations/defaults/presets/ftrack/project_defaults.json b/pype/configurations/defaults/project_configurations/ftrack/project_defaults.json similarity index 100% rename from pype/configurations/defaults/presets/ftrack/project_defaults.json rename to pype/configurations/defaults/project_configurations/ftrack/project_defaults.json diff --git a/pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json b/pype/configurations/defaults/project_configurations/ftrack/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json rename to pype/configurations/defaults/project_configurations/ftrack/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/create.json b/pype/configurations/defaults/project_configurations/global/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/create.json rename to pype/configurations/defaults/project_configurations/global/create.json diff --git a/pype/configurations/defaults/presets/tools/creator.json b/pype/configurations/defaults/project_configurations/global/creator.json similarity index 100% rename from pype/configurations/defaults/presets/tools/creator.json rename to pype/configurations/defaults/project_configurations/global/creator.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/filter.json b/pype/configurations/defaults/project_configurations/global/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/filter.json rename to pype/configurations/defaults/project_configurations/global/filter.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/load.json b/pype/configurations/defaults/project_configurations/global/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/load.json rename to pype/configurations/defaults/project_configurations/global/load.json diff --git a/pype/configurations/defaults/presets/tools/project_folder_structure.json b/pype/configurations/defaults/project_configurations/global/project_folder_structure.json similarity index 100% rename from pype/configurations/defaults/presets/tools/project_folder_structure.json rename to pype/configurations/defaults/project_configurations/global/project_folder_structure.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/publish.json b/pype/configurations/defaults/project_configurations/global/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/publish.json rename to pype/configurations/defaults/project_configurations/global/publish.json diff --git a/pype/configurations/defaults/presets/tools/sw_folders.json b/pype/configurations/defaults/project_configurations/global/sw_folders.json similarity index 100% rename from pype/configurations/defaults/presets/tools/sw_folders.json rename to pype/configurations/defaults/project_configurations/global/sw_folders.json diff --git a/pype/configurations/defaults/presets/tools/workfiles.json b/pype/configurations/defaults/project_configurations/global/workfiles.json similarity index 100% rename from pype/configurations/defaults/presets/tools/workfiles.json rename to pype/configurations/defaults/project_configurations/global/workfiles.json diff --git a/pype/configurations/defaults/presets/maya/capture.json b/pype/configurations/defaults/project_configurations/maya/capture.json similarity index 100% rename from pype/configurations/defaults/presets/maya/capture.json rename to pype/configurations/defaults/project_configurations/maya/capture.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/create.json b/pype/configurations/defaults/project_configurations/maya/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/create.json rename to pype/configurations/defaults/project_configurations/maya/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/filter.json b/pype/configurations/defaults/project_configurations/maya/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/filter.json rename to pype/configurations/defaults/project_configurations/maya/filter.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/load.json b/pype/configurations/defaults/project_configurations/maya/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/load.json rename to pype/configurations/defaults/project_configurations/maya/load.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/publish.json b/pype/configurations/defaults/project_configurations/maya/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/publish.json rename to pype/configurations/defaults/project_configurations/maya/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json b/pype/configurations/defaults/project_configurations/maya/workfile_build.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json rename to pype/configurations/defaults/project_configurations/maya/workfile_build.json diff --git a/pype/configurations/defaults/presets/muster/templates_mapping.json b/pype/configurations/defaults/project_configurations/muster/templates_mapping.json similarity index 100% rename from pype/configurations/defaults/presets/muster/templates_mapping.json rename to pype/configurations/defaults/project_configurations/muster/templates_mapping.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/create.json b/pype/configurations/defaults/project_configurations/nuke/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/create.json rename to pype/configurations/defaults/project_configurations/nuke/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/load.json b/pype/configurations/defaults/project_configurations/nuke/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/load.json rename to pype/configurations/defaults/project_configurations/nuke/load.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/publish.json b/pype/configurations/defaults/project_configurations/nuke/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/publish.json rename to pype/configurations/defaults/project_configurations/nuke/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json b/pype/configurations/defaults/project_configurations/nuke/workfile_build.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json rename to pype/configurations/defaults/project_configurations/nuke/workfile_build.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json b/pype/configurations/defaults/project_configurations/nukestudio/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json rename to pype/configurations/defaults/project_configurations/nukestudio/filter.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json b/pype/configurations/defaults/project_configurations/nukestudio/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json rename to pype/configurations/defaults/project_configurations/nukestudio/publish.json diff --git a/pype/configurations/defaults/presets/nukestudio/tags.json b/pype/configurations/defaults/project_configurations/nukestudio/tags.json similarity index 100% rename from pype/configurations/defaults/presets/nukestudio/tags.json rename to pype/configurations/defaults/project_configurations/nukestudio/tags.json diff --git a/pype/configurations/defaults/presets/premiere/asset_default.json b/pype/configurations/defaults/project_configurations/premiere/asset_default.json similarity index 100% rename from pype/configurations/defaults/presets/premiere/asset_default.json rename to pype/configurations/defaults/project_configurations/premiere/asset_default.json diff --git a/pype/configurations/defaults/presets/premiere/rules_tasks.json b/pype/configurations/defaults/project_configurations/premiere/rules_tasks.json similarity index 100% rename from pype/configurations/defaults/presets/premiere/rules_tasks.json rename to pype/configurations/defaults/project_configurations/premiere/rules_tasks.json diff --git a/pype/configurations/defaults/project_configurations/plugins/resolve/create.json b/pype/configurations/defaults/project_configurations/resolve/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/resolve/create.json rename to pype/configurations/defaults/project_configurations/resolve/create.json diff --git a/pype/configurations/defaults/presets/standalone_publish/families.json b/pype/configurations/defaults/project_configurations/standalonepublisher/families.json similarity index 100% rename from pype/configurations/defaults/presets/standalone_publish/families.json rename to pype/configurations/defaults/project_configurations/standalonepublisher/families.json diff --git a/pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json b/pype/configurations/defaults/project_configurations/standalonepublisher/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json rename to pype/configurations/defaults/project_configurations/standalonepublisher/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/test/create.json b/pype/configurations/defaults/project_configurations/test/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/test/create.json rename to pype/configurations/defaults/project_configurations/test/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/test/publish.json b/pype/configurations/defaults/project_configurations/test/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/test/publish.json rename to pype/configurations/defaults/project_configurations/test/publish.json diff --git a/pype/configurations/defaults/presets/tools/slates/example_HD.json b/pype/configurations/defaults/project_configurations/tools/slates/example_HD.json similarity index 100% rename from pype/configurations/defaults/presets/tools/slates/example_HD.json rename to pype/configurations/defaults/project_configurations/tools/slates/example_HD.json diff --git a/pype/configurations/defaults/presets/unreal/project_setup.json b/pype/configurations/defaults/project_configurations/unreal/project_setup.json similarity index 100% rename from pype/configurations/defaults/presets/unreal/project_setup.json rename to pype/configurations/defaults/project_configurations/unreal/project_setup.json diff --git a/pype/configurations/defaults/environments/avalon.json b/pype/configurations/defaults/studio_configurations/environments/avalon.json similarity index 100% rename from pype/configurations/defaults/environments/avalon.json rename to pype/configurations/defaults/studio_configurations/environments/avalon.json diff --git a/pype/configurations/defaults/environments/blender.json b/pype/configurations/defaults/studio_configurations/environments/blender.json similarity index 100% rename from pype/configurations/defaults/environments/blender.json rename to pype/configurations/defaults/studio_configurations/environments/blender.json diff --git a/pype/configurations/defaults/environments/celaction.json b/pype/configurations/defaults/studio_configurations/environments/celaction.json similarity index 100% rename from pype/configurations/defaults/environments/celaction.json rename to pype/configurations/defaults/studio_configurations/environments/celaction.json diff --git a/pype/configurations/defaults/environments/deadline.json b/pype/configurations/defaults/studio_configurations/environments/deadline.json similarity index 100% rename from pype/configurations/defaults/environments/deadline.json rename to pype/configurations/defaults/studio_configurations/environments/deadline.json diff --git a/pype/configurations/defaults/environments/ftrack.json b/pype/configurations/defaults/studio_configurations/environments/ftrack.json similarity index 100% rename from pype/configurations/defaults/environments/ftrack.json rename to pype/configurations/defaults/studio_configurations/environments/ftrack.json diff --git a/pype/configurations/defaults/environments/global.json b/pype/configurations/defaults/studio_configurations/environments/global.json similarity index 100% rename from pype/configurations/defaults/environments/global.json rename to pype/configurations/defaults/studio_configurations/environments/global.json diff --git a/pype/configurations/defaults/environments/harmony.json b/pype/configurations/defaults/studio_configurations/environments/harmony.json similarity index 100% rename from pype/configurations/defaults/environments/harmony.json rename to pype/configurations/defaults/studio_configurations/environments/harmony.json diff --git a/pype/configurations/defaults/environments/houdini.json b/pype/configurations/defaults/studio_configurations/environments/houdini.json similarity index 100% rename from pype/configurations/defaults/environments/houdini.json rename to pype/configurations/defaults/studio_configurations/environments/houdini.json diff --git a/pype/configurations/defaults/environments/maya.json b/pype/configurations/defaults/studio_configurations/environments/maya.json similarity index 100% rename from pype/configurations/defaults/environments/maya.json rename to pype/configurations/defaults/studio_configurations/environments/maya.json diff --git a/pype/configurations/defaults/environments/maya_2018.json b/pype/configurations/defaults/studio_configurations/environments/maya_2018.json similarity index 100% rename from pype/configurations/defaults/environments/maya_2018.json rename to pype/configurations/defaults/studio_configurations/environments/maya_2018.json diff --git a/pype/configurations/defaults/environments/maya_2020.json b/pype/configurations/defaults/studio_configurations/environments/maya_2020.json similarity index 100% rename from pype/configurations/defaults/environments/maya_2020.json rename to pype/configurations/defaults/studio_configurations/environments/maya_2020.json diff --git a/pype/configurations/defaults/environments/mayabatch.json b/pype/configurations/defaults/studio_configurations/environments/mayabatch.json similarity index 100% rename from pype/configurations/defaults/environments/mayabatch.json rename to pype/configurations/defaults/studio_configurations/environments/mayabatch.json diff --git a/pype/configurations/defaults/environments/mayabatch_2019.json b/pype/configurations/defaults/studio_configurations/environments/mayabatch_2019.json similarity index 100% rename from pype/configurations/defaults/environments/mayabatch_2019.json rename to pype/configurations/defaults/studio_configurations/environments/mayabatch_2019.json diff --git a/pype/configurations/defaults/environments/mtoa_3.1.1.json b/pype/configurations/defaults/studio_configurations/environments/mtoa_3.1.1.json similarity index 100% rename from pype/configurations/defaults/environments/mtoa_3.1.1.json rename to pype/configurations/defaults/studio_configurations/environments/mtoa_3.1.1.json diff --git a/pype/configurations/defaults/environments/muster.json b/pype/configurations/defaults/studio_configurations/environments/muster.json similarity index 100% rename from pype/configurations/defaults/environments/muster.json rename to pype/configurations/defaults/studio_configurations/environments/muster.json diff --git a/pype/configurations/defaults/environments/nuke.json b/pype/configurations/defaults/studio_configurations/environments/nuke.json similarity index 100% rename from pype/configurations/defaults/environments/nuke.json rename to pype/configurations/defaults/studio_configurations/environments/nuke.json diff --git a/pype/configurations/defaults/environments/nukestudio.json b/pype/configurations/defaults/studio_configurations/environments/nukestudio.json similarity index 100% rename from pype/configurations/defaults/environments/nukestudio.json rename to pype/configurations/defaults/studio_configurations/environments/nukestudio.json diff --git a/pype/configurations/defaults/environments/nukestudio_10.0.json b/pype/configurations/defaults/studio_configurations/environments/nukestudio_10.0.json similarity index 100% rename from pype/configurations/defaults/environments/nukestudio_10.0.json rename to pype/configurations/defaults/studio_configurations/environments/nukestudio_10.0.json diff --git a/pype/configurations/defaults/environments/nukex.json b/pype/configurations/defaults/studio_configurations/environments/nukex.json similarity index 100% rename from pype/configurations/defaults/environments/nukex.json rename to pype/configurations/defaults/studio_configurations/environments/nukex.json diff --git a/pype/configurations/defaults/environments/nukex_10.0.json b/pype/configurations/defaults/studio_configurations/environments/nukex_10.0.json similarity index 100% rename from pype/configurations/defaults/environments/nukex_10.0.json rename to pype/configurations/defaults/studio_configurations/environments/nukex_10.0.json diff --git a/pype/configurations/defaults/environments/photoshop.json b/pype/configurations/defaults/studio_configurations/environments/photoshop.json similarity index 100% rename from pype/configurations/defaults/environments/photoshop.json rename to pype/configurations/defaults/studio_configurations/environments/photoshop.json diff --git a/pype/configurations/defaults/environments/premiere.json b/pype/configurations/defaults/studio_configurations/environments/premiere.json similarity index 100% rename from pype/configurations/defaults/environments/premiere.json rename to pype/configurations/defaults/studio_configurations/environments/premiere.json diff --git a/pype/configurations/defaults/environments/resolve.json b/pype/configurations/defaults/studio_configurations/environments/resolve.json similarity index 100% rename from pype/configurations/defaults/environments/resolve.json rename to pype/configurations/defaults/studio_configurations/environments/resolve.json diff --git a/pype/configurations/defaults/environments/storyboardpro.json b/pype/configurations/defaults/studio_configurations/environments/storyboardpro.json similarity index 100% rename from pype/configurations/defaults/environments/storyboardpro.json rename to pype/configurations/defaults/studio_configurations/environments/storyboardpro.json diff --git a/pype/configurations/defaults/environments/unreal_4.24.json b/pype/configurations/defaults/studio_configurations/environments/unreal_4.24.json similarity index 100% rename from pype/configurations/defaults/environments/unreal_4.24.json rename to pype/configurations/defaults/studio_configurations/environments/unreal_4.24.json diff --git a/pype/configurations/defaults/environments/vray_4300.json b/pype/configurations/defaults/studio_configurations/environments/vray_4300.json similarity index 100% rename from pype/configurations/defaults/environments/vray_4300.json rename to pype/configurations/defaults/studio_configurations/environments/vray_4300.json diff --git a/pype/configurations/defaults/launchers/blender_2.80.toml b/pype/configurations/defaults/studio_configurations/launchers/blender_2.80.toml similarity index 100% rename from pype/configurations/defaults/launchers/blender_2.80.toml rename to pype/configurations/defaults/studio_configurations/launchers/blender_2.80.toml diff --git a/pype/configurations/defaults/launchers/blender_2.81.toml b/pype/configurations/defaults/studio_configurations/launchers/blender_2.81.toml similarity index 100% rename from pype/configurations/defaults/launchers/blender_2.81.toml rename to pype/configurations/defaults/studio_configurations/launchers/blender_2.81.toml diff --git a/pype/configurations/defaults/launchers/blender_2.82.toml b/pype/configurations/defaults/studio_configurations/launchers/blender_2.82.toml similarity index 100% rename from pype/configurations/defaults/launchers/blender_2.82.toml rename to pype/configurations/defaults/studio_configurations/launchers/blender_2.82.toml diff --git a/pype/configurations/defaults/launchers/blender_2.83.toml b/pype/configurations/defaults/studio_configurations/launchers/blender_2.83.toml similarity index 100% rename from pype/configurations/defaults/launchers/blender_2.83.toml rename to pype/configurations/defaults/studio_configurations/launchers/blender_2.83.toml diff --git a/pype/configurations/defaults/launchers/celaction_local.toml b/pype/configurations/defaults/studio_configurations/launchers/celaction_local.toml similarity index 100% rename from pype/configurations/defaults/launchers/celaction_local.toml rename to pype/configurations/defaults/studio_configurations/launchers/celaction_local.toml diff --git a/pype/configurations/defaults/launchers/celaction_publish.toml b/pype/configurations/defaults/studio_configurations/launchers/celaction_publish.toml similarity index 100% rename from pype/configurations/defaults/launchers/celaction_publish.toml rename to pype/configurations/defaults/studio_configurations/launchers/celaction_publish.toml diff --git a/pype/configurations/defaults/launchers/darwin/blender_2.82 b/pype/configurations/defaults/studio_configurations/launchers/darwin/blender_2.82 similarity index 100% rename from pype/configurations/defaults/launchers/darwin/blender_2.82 rename to pype/configurations/defaults/studio_configurations/launchers/darwin/blender_2.82 diff --git a/pype/configurations/defaults/launchers/darwin/harmony_17 b/pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17 similarity index 100% rename from pype/configurations/defaults/launchers/darwin/harmony_17 rename to pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17 diff --git a/pype/configurations/defaults/launchers/darwin/harmony_17_launch b/pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17_launch similarity index 100% rename from pype/configurations/defaults/launchers/darwin/harmony_17_launch rename to pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17_launch diff --git a/pype/configurations/defaults/launchers/darwin/python3 b/pype/configurations/defaults/studio_configurations/launchers/darwin/python3 similarity index 100% rename from pype/configurations/defaults/launchers/darwin/python3 rename to pype/configurations/defaults/studio_configurations/launchers/darwin/python3 diff --git a/pype/configurations/defaults/launchers/harmony_17.toml b/pype/configurations/defaults/studio_configurations/launchers/harmony_17.toml similarity index 100% rename from pype/configurations/defaults/launchers/harmony_17.toml rename to pype/configurations/defaults/studio_configurations/launchers/harmony_17.toml diff --git a/pype/configurations/defaults/launchers/houdini_16.toml b/pype/configurations/defaults/studio_configurations/launchers/houdini_16.toml similarity index 100% rename from pype/configurations/defaults/launchers/houdini_16.toml rename to pype/configurations/defaults/studio_configurations/launchers/houdini_16.toml diff --git a/pype/configurations/defaults/launchers/houdini_17.toml b/pype/configurations/defaults/studio_configurations/launchers/houdini_17.toml similarity index 100% rename from pype/configurations/defaults/launchers/houdini_17.toml rename to pype/configurations/defaults/studio_configurations/launchers/houdini_17.toml diff --git a/pype/configurations/defaults/launchers/houdini_18.toml b/pype/configurations/defaults/studio_configurations/launchers/houdini_18.toml similarity index 100% rename from pype/configurations/defaults/launchers/houdini_18.toml rename to pype/configurations/defaults/studio_configurations/launchers/houdini_18.toml diff --git a/pype/configurations/defaults/launchers/linux/maya2016 b/pype/configurations/defaults/studio_configurations/launchers/linux/maya2016 similarity index 100% rename from pype/configurations/defaults/launchers/linux/maya2016 rename to pype/configurations/defaults/studio_configurations/launchers/linux/maya2016 diff --git a/pype/configurations/defaults/launchers/linux/maya2017 b/pype/configurations/defaults/studio_configurations/launchers/linux/maya2017 similarity index 100% rename from pype/configurations/defaults/launchers/linux/maya2017 rename to pype/configurations/defaults/studio_configurations/launchers/linux/maya2017 diff --git a/pype/configurations/defaults/launchers/linux/maya2018 b/pype/configurations/defaults/studio_configurations/launchers/linux/maya2018 similarity index 100% rename from pype/configurations/defaults/launchers/linux/maya2018 rename to pype/configurations/defaults/studio_configurations/launchers/linux/maya2018 diff --git a/pype/configurations/defaults/launchers/linux/maya2019 b/pype/configurations/defaults/studio_configurations/launchers/linux/maya2019 similarity index 100% rename from pype/configurations/defaults/launchers/linux/maya2019 rename to pype/configurations/defaults/studio_configurations/launchers/linux/maya2019 diff --git a/pype/configurations/defaults/launchers/linux/maya2020 b/pype/configurations/defaults/studio_configurations/launchers/linux/maya2020 similarity index 100% rename from pype/configurations/defaults/launchers/linux/maya2020 rename to pype/configurations/defaults/studio_configurations/launchers/linux/maya2020 diff --git a/pype/configurations/defaults/launchers/linux/nuke11.3 b/pype/configurations/defaults/studio_configurations/launchers/linux/nuke11.3 similarity index 100% rename from pype/configurations/defaults/launchers/linux/nuke11.3 rename to pype/configurations/defaults/studio_configurations/launchers/linux/nuke11.3 diff --git a/pype/configurations/defaults/launchers/linux/nuke12.0 b/pype/configurations/defaults/studio_configurations/launchers/linux/nuke12.0 similarity index 100% rename from pype/configurations/defaults/launchers/linux/nuke12.0 rename to pype/configurations/defaults/studio_configurations/launchers/linux/nuke12.0 diff --git a/pype/configurations/defaults/launchers/linux/nukestudio11.3 b/pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio11.3 similarity index 100% rename from pype/configurations/defaults/launchers/linux/nukestudio11.3 rename to pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio11.3 diff --git a/pype/configurations/defaults/launchers/linux/nukestudio12.0 b/pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio12.0 similarity index 100% rename from pype/configurations/defaults/launchers/linux/nukestudio12.0 rename to pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio12.0 diff --git a/pype/configurations/defaults/launchers/linux/nukex11.3 b/pype/configurations/defaults/studio_configurations/launchers/linux/nukex11.3 similarity index 100% rename from pype/configurations/defaults/launchers/linux/nukex11.3 rename to pype/configurations/defaults/studio_configurations/launchers/linux/nukex11.3 diff --git a/pype/configurations/defaults/launchers/linux/nukex12.0 b/pype/configurations/defaults/studio_configurations/launchers/linux/nukex12.0 similarity index 100% rename from pype/configurations/defaults/launchers/linux/nukex12.0 rename to pype/configurations/defaults/studio_configurations/launchers/linux/nukex12.0 diff --git a/pype/configurations/defaults/launchers/maya_2016.toml b/pype/configurations/defaults/studio_configurations/launchers/maya_2016.toml similarity index 100% rename from pype/configurations/defaults/launchers/maya_2016.toml rename to pype/configurations/defaults/studio_configurations/launchers/maya_2016.toml diff --git a/pype/configurations/defaults/launchers/maya_2017.toml b/pype/configurations/defaults/studio_configurations/launchers/maya_2017.toml similarity index 100% rename from pype/configurations/defaults/launchers/maya_2017.toml rename to pype/configurations/defaults/studio_configurations/launchers/maya_2017.toml diff --git a/pype/configurations/defaults/launchers/maya_2018.toml b/pype/configurations/defaults/studio_configurations/launchers/maya_2018.toml similarity index 100% rename from pype/configurations/defaults/launchers/maya_2018.toml rename to pype/configurations/defaults/studio_configurations/launchers/maya_2018.toml diff --git a/pype/configurations/defaults/launchers/maya_2019.toml b/pype/configurations/defaults/studio_configurations/launchers/maya_2019.toml similarity index 100% rename from pype/configurations/defaults/launchers/maya_2019.toml rename to pype/configurations/defaults/studio_configurations/launchers/maya_2019.toml diff --git a/pype/configurations/defaults/launchers/maya_2020.toml b/pype/configurations/defaults/studio_configurations/launchers/maya_2020.toml similarity index 100% rename from pype/configurations/defaults/launchers/maya_2020.toml rename to pype/configurations/defaults/studio_configurations/launchers/maya_2020.toml diff --git a/pype/configurations/defaults/launchers/mayabatch_2019.toml b/pype/configurations/defaults/studio_configurations/launchers/mayabatch_2019.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayabatch_2019.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayabatch_2019.toml diff --git a/pype/configurations/defaults/launchers/mayabatch_2020.toml b/pype/configurations/defaults/studio_configurations/launchers/mayabatch_2020.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayabatch_2020.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayabatch_2020.toml diff --git a/pype/configurations/defaults/launchers/mayapy2016.toml b/pype/configurations/defaults/studio_configurations/launchers/mayapy2016.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayapy2016.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayapy2016.toml diff --git a/pype/configurations/defaults/launchers/mayapy2017.toml b/pype/configurations/defaults/studio_configurations/launchers/mayapy2017.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayapy2017.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayapy2017.toml diff --git a/pype/configurations/defaults/launchers/mayapy2018.toml b/pype/configurations/defaults/studio_configurations/launchers/mayapy2018.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayapy2018.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayapy2018.toml diff --git a/pype/configurations/defaults/launchers/mayapy2019.toml b/pype/configurations/defaults/studio_configurations/launchers/mayapy2019.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayapy2019.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayapy2019.toml diff --git a/pype/configurations/defaults/launchers/mayapy2020.toml b/pype/configurations/defaults/studio_configurations/launchers/mayapy2020.toml similarity index 100% rename from pype/configurations/defaults/launchers/mayapy2020.toml rename to pype/configurations/defaults/studio_configurations/launchers/mayapy2020.toml diff --git a/pype/configurations/defaults/launchers/myapp.toml b/pype/configurations/defaults/studio_configurations/launchers/myapp.toml similarity index 100% rename from pype/configurations/defaults/launchers/myapp.toml rename to pype/configurations/defaults/studio_configurations/launchers/myapp.toml diff --git a/pype/configurations/defaults/launchers/nuke_10.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nuke_10.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nuke_10.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nuke_10.0.toml diff --git a/pype/configurations/defaults/launchers/nuke_11.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nuke_11.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nuke_11.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nuke_11.0.toml diff --git a/pype/configurations/defaults/launchers/nuke_11.2.toml b/pype/configurations/defaults/studio_configurations/launchers/nuke_11.2.toml similarity index 100% rename from pype/configurations/defaults/launchers/nuke_11.2.toml rename to pype/configurations/defaults/studio_configurations/launchers/nuke_11.2.toml diff --git a/pype/configurations/defaults/launchers/nuke_11.3.toml b/pype/configurations/defaults/studio_configurations/launchers/nuke_11.3.toml similarity index 100% rename from pype/configurations/defaults/launchers/nuke_11.3.toml rename to pype/configurations/defaults/studio_configurations/launchers/nuke_11.3.toml diff --git a/pype/configurations/defaults/launchers/nuke_12.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nuke_12.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nuke_12.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nuke_12.0.toml diff --git a/pype/configurations/defaults/launchers/nukestudio_10.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nukestudio_10.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukestudio_10.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukestudio_10.0.toml diff --git a/pype/configurations/defaults/launchers/nukestudio_11.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukestudio_11.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.0.toml diff --git a/pype/configurations/defaults/launchers/nukestudio_11.2.toml b/pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.2.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukestudio_11.2.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.2.toml diff --git a/pype/configurations/defaults/launchers/nukestudio_11.3.toml b/pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.3.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukestudio_11.3.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.3.toml diff --git a/pype/configurations/defaults/launchers/nukestudio_12.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nukestudio_12.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukestudio_12.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukestudio_12.0.toml diff --git a/pype/configurations/defaults/launchers/nukex_10.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nukex_10.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukex_10.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukex_10.0.toml diff --git a/pype/configurations/defaults/launchers/nukex_11.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nukex_11.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukex_11.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukex_11.0.toml diff --git a/pype/configurations/defaults/launchers/nukex_11.2.toml b/pype/configurations/defaults/studio_configurations/launchers/nukex_11.2.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukex_11.2.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukex_11.2.toml diff --git a/pype/configurations/defaults/launchers/nukex_11.3.toml b/pype/configurations/defaults/studio_configurations/launchers/nukex_11.3.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukex_11.3.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukex_11.3.toml diff --git a/pype/configurations/defaults/launchers/nukex_12.0.toml b/pype/configurations/defaults/studio_configurations/launchers/nukex_12.0.toml similarity index 100% rename from pype/configurations/defaults/launchers/nukex_12.0.toml rename to pype/configurations/defaults/studio_configurations/launchers/nukex_12.0.toml diff --git a/pype/configurations/defaults/launchers/photoshop_2020.toml b/pype/configurations/defaults/studio_configurations/launchers/photoshop_2020.toml similarity index 100% rename from pype/configurations/defaults/launchers/photoshop_2020.toml rename to pype/configurations/defaults/studio_configurations/launchers/photoshop_2020.toml diff --git a/pype/configurations/defaults/launchers/premiere_2019.toml b/pype/configurations/defaults/studio_configurations/launchers/premiere_2019.toml similarity index 100% rename from pype/configurations/defaults/launchers/premiere_2019.toml rename to pype/configurations/defaults/studio_configurations/launchers/premiere_2019.toml diff --git a/pype/configurations/defaults/launchers/premiere_2020.toml b/pype/configurations/defaults/studio_configurations/launchers/premiere_2020.toml similarity index 100% rename from pype/configurations/defaults/launchers/premiere_2020.toml rename to pype/configurations/defaults/studio_configurations/launchers/premiere_2020.toml diff --git a/pype/configurations/defaults/launchers/python_2.toml b/pype/configurations/defaults/studio_configurations/launchers/python_2.toml similarity index 100% rename from pype/configurations/defaults/launchers/python_2.toml rename to pype/configurations/defaults/studio_configurations/launchers/python_2.toml diff --git a/pype/configurations/defaults/launchers/python_3.toml b/pype/configurations/defaults/studio_configurations/launchers/python_3.toml similarity index 100% rename from pype/configurations/defaults/launchers/python_3.toml rename to pype/configurations/defaults/studio_configurations/launchers/python_3.toml diff --git a/pype/configurations/defaults/launchers/resolve_16.toml b/pype/configurations/defaults/studio_configurations/launchers/resolve_16.toml similarity index 100% rename from pype/configurations/defaults/launchers/resolve_16.toml rename to pype/configurations/defaults/studio_configurations/launchers/resolve_16.toml diff --git a/pype/configurations/defaults/launchers/shell.toml b/pype/configurations/defaults/studio_configurations/launchers/shell.toml similarity index 100% rename from pype/configurations/defaults/launchers/shell.toml rename to pype/configurations/defaults/studio_configurations/launchers/shell.toml diff --git a/pype/configurations/defaults/launchers/storyboardpro_7.toml b/pype/configurations/defaults/studio_configurations/launchers/storyboardpro_7.toml similarity index 100% rename from pype/configurations/defaults/launchers/storyboardpro_7.toml rename to pype/configurations/defaults/studio_configurations/launchers/storyboardpro_7.toml diff --git a/pype/configurations/defaults/launchers/unreal_4.24.toml b/pype/configurations/defaults/studio_configurations/launchers/unreal_4.24.toml similarity index 100% rename from pype/configurations/defaults/launchers/unreal_4.24.toml rename to pype/configurations/defaults/studio_configurations/launchers/unreal_4.24.toml diff --git a/pype/configurations/defaults/launchers/windows/blender_2.80.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.80.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/blender_2.80.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.80.bat diff --git a/pype/configurations/defaults/launchers/windows/blender_2.81.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.81.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/blender_2.81.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.81.bat diff --git a/pype/configurations/defaults/launchers/windows/blender_2.82.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.82.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/blender_2.82.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.82.bat diff --git a/pype/configurations/defaults/launchers/windows/blender_2.83.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.83.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/blender_2.83.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.83.bat diff --git a/pype/configurations/defaults/launchers/windows/celaction_local.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/celaction_local.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/celaction_local.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/celaction_local.bat diff --git a/pype/configurations/defaults/launchers/windows/celaction_publish.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/celaction_publish.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/celaction_publish.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/celaction_publish.bat diff --git a/pype/configurations/defaults/launchers/windows/harmony_17.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/harmony_17.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/harmony_17.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/harmony_17.bat diff --git a/pype/configurations/defaults/launchers/windows/houdini_16.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/houdini_16.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/houdini_16.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/houdini_16.bat diff --git a/pype/configurations/defaults/launchers/windows/houdini_17.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/houdini_17.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/houdini_17.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/houdini_17.bat diff --git a/pype/configurations/defaults/launchers/windows/houdini_18.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/houdini_18.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/houdini_18.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/houdini_18.bat diff --git a/pype/configurations/defaults/launchers/windows/maya2016.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/maya2016.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/maya2016.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/maya2016.bat diff --git a/pype/configurations/defaults/launchers/windows/maya2017.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/maya2017.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/maya2017.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/maya2017.bat diff --git a/pype/configurations/defaults/launchers/windows/maya2018.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/maya2018.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/maya2018.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/maya2018.bat diff --git a/pype/configurations/defaults/launchers/windows/maya2019.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/maya2019.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/maya2019.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/maya2019.bat diff --git a/pype/configurations/defaults/launchers/windows/maya2020.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/maya2020.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/maya2020.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/maya2020.bat diff --git a/pype/configurations/defaults/launchers/windows/mayabatch2019.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2019.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayabatch2019.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2019.bat diff --git a/pype/configurations/defaults/launchers/windows/mayabatch2020.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2020.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayabatch2020.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2020.bat diff --git a/pype/configurations/defaults/launchers/windows/mayapy2016.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2016.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayapy2016.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2016.bat diff --git a/pype/configurations/defaults/launchers/windows/mayapy2017.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2017.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayapy2017.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2017.bat diff --git a/pype/configurations/defaults/launchers/windows/mayapy2018.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2018.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayapy2018.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2018.bat diff --git a/pype/configurations/defaults/launchers/windows/mayapy2019.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2019.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayapy2019.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2019.bat diff --git a/pype/configurations/defaults/launchers/windows/mayapy2020.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2020.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/mayapy2020.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2020.bat diff --git a/pype/configurations/defaults/launchers/windows/nuke10.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nuke10.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nuke10.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nuke10.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nuke11.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nuke11.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nuke11.2.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.2.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nuke11.2.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.2.bat diff --git a/pype/configurations/defaults/launchers/windows/nuke11.3.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.3.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nuke11.3.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.3.bat diff --git a/pype/configurations/defaults/launchers/windows/nuke12.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nuke12.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nuke12.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nuke12.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nukestudio10.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio10.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukestudio10.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio10.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nukestudio11.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukestudio11.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nukestudio11.2.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.2.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukestudio11.2.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.2.bat diff --git a/pype/configurations/defaults/launchers/windows/nukestudio11.3.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.3.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukestudio11.3.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.3.bat diff --git a/pype/configurations/defaults/launchers/windows/nukestudio12.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio12.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukestudio12.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio12.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nukex10.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukex10.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukex10.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukex10.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nukex11.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukex11.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.0.bat diff --git a/pype/configurations/defaults/launchers/windows/nukex11.2.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.2.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukex11.2.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.2.bat diff --git a/pype/configurations/defaults/launchers/windows/nukex11.3.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.3.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukex11.3.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.3.bat diff --git a/pype/configurations/defaults/launchers/windows/nukex12.0.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/nukex12.0.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/nukex12.0.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/nukex12.0.bat diff --git a/pype/configurations/defaults/launchers/windows/photoshop_2020.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/photoshop_2020.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/photoshop_2020.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/photoshop_2020.bat diff --git a/pype/configurations/defaults/launchers/windows/premiere_pro_2019.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2019.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/premiere_pro_2019.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2019.bat diff --git a/pype/configurations/defaults/launchers/windows/premiere_pro_2020.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2020.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/premiere_pro_2020.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2020.bat diff --git a/pype/configurations/defaults/launchers/windows/python3.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/python3.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/python3.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/python3.bat diff --git a/pype/configurations/defaults/launchers/windows/resolve_16.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/resolve_16.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/resolve_16.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/resolve_16.bat diff --git a/pype/configurations/defaults/launchers/windows/shell.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/shell.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/shell.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/shell.bat diff --git a/pype/configurations/defaults/launchers/windows/storyboardpro_7.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/storyboardpro_7.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/storyboardpro_7.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/storyboardpro_7.bat diff --git a/pype/configurations/defaults/launchers/windows/unreal.bat b/pype/configurations/defaults/studio_configurations/launchers/windows/unreal.bat similarity index 100% rename from pype/configurations/defaults/launchers/windows/unreal.bat rename to pype/configurations/defaults/studio_configurations/launchers/windows/unreal.bat From 7d7a2e1b230584d4cc99b126ac155c4434f408c0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:37:11 +0200 Subject: [PATCH 780/947] initial commit to save studio configurations to different place --- pype/configurations/config.py | 43 +++++++++++++------ .../config_setting/widgets/base.py | 3 +- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/pype/configurations/config.py b/pype/configurations/config.py index 416704649c..147570acd4 100644 --- a/pype/configurations/config.py +++ b/pype/configurations/config.py @@ -5,18 +5,37 @@ import copy log = logging.getLogger(__name__) -STUDIO_PRESETS_PATH = os.path.normpath( - os.path.join(os.environ["PYPE_CONFIG"], "studio_configurations") +# Metadata keys for work with studio and project overrides +OVERRIDEN_KEY = "__overriden_keys__" +# NOTE key popping not implemented yet +POP_KEY = "__pop_key__" + +# Paths to studio and project overrides +STUDIO_OVERRIDES_PATH = os.environ["PYPE_CONFIG"] + +SYSTEM_CONFIGURATIONS_DIR = "studio_configurations" +SYSTEM_CONFIGURATIONS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, SYSTEM_CONFIGURATIONS_DIR ) -PROJECT_CONFIGURATION_DIR = "project_configurations" -PROJECT_PRESETS_PATH = os.path.normpath(os.path.join( - os.environ["PYPE_CONFIG"], PROJECT_CONFIGURATION_DIR -)) +PROJECT_CONFIGURATIONS_DIR = "project_configurations" +PROJECT_CONFIGURATIONS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, PROJECT_CONFIGURATIONS_DIR +) + +# Variable where cache of default configurations are stored +_DEFAULT_CONFIGURATIONS = None + +# TODO remove this as is maybe deprecated first_run = False -# TODO key popping not implemented yet -POP_KEY = "__pop_key__" -OVERRIDEN_KEY = "__overriden_keys__" + +def default_configuration(): + global _DEFAULT_CONFIGURATIONS + if _DEFAULT_CONFIGURATIONS is None: + current_dir = os.path.dirname(__file__) + defaults_path = os.path.join(current_dir, "defaults") + _DEFAULT_CONFIGURATIONS = load_jsons_from_dir(defaults_path) + return _DEFAULT_CONFIGURATIONS def load_json(fpath): @@ -129,17 +148,17 @@ def load_jsons_from_dir(path, *args, **kwargs): def studio_configurations(*args, **kwargs): - return load_jsons_from_dir(STUDIO_PRESETS_PATH, *args, **kwargs) + return load_jsons_from_dir(SYSTEM_CONFIGURATIONS_PATH, *args, **kwargs) def global_project_configurations(**kwargs): - return load_jsons_from_dir(PROJECT_PRESETS_PATH, **kwargs) + return load_jsons_from_dir(PROJECT_CONFIGURATIONS_PATH, **kwargs) def path_to_project_overrides(project_name): project_configs_path = os.environ["PYPE_PROJECT_CONFIGS"] dirpath = os.path.join(project_configs_path, project_name) - return os.path.join(dirpath, PROJECT_CONFIGURATION_DIR + ".json") + return os.path.join(dirpath, PROJECT_CONFIGURATIONS_DIR + ".json") def project_configurations_overrides(project_name, **kwargs): diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index d39b5789d9..0660ab1e06 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -156,7 +156,7 @@ class SystemWidget(QtWidgets.QWidget): for key in key_sequence: new_values = new_values[key] origin_values.update(new_values) - + raise NotImplementedError("Output from global values has changed") output_path = os.path.join( config.STUDIO_PRESETS_PATH, subpath ) @@ -537,6 +537,7 @@ class ProjectWidget(QtWidgets.QWidget): else: origin_values = new_values + raise NotImplementedError("Output from global values has changed") output_path = os.path.join( config.PROJECT_PRESETS_PATH, subpath ) From 4fabd706e820d45755608b7605c32741ab4da5a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:54:16 +0200 Subject: [PATCH 781/947] renamed studio to system --- .../studio_schema/0_studio_gui_schema.json | 3 +-- .../config_setting/widgets/base.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json index bde340250e..de17328860 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json @@ -1,7 +1,6 @@ { - "key": "studio", + "key": "system", "type": "dict-invisible", - "label": "Studio", "children": [ { "type": "dict-invisible", diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 0660ab1e06..3d255b416e 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -95,7 +95,7 @@ class SystemWidget(QtWidgets.QWidget): self.schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") self.keys = self.schema.get("keys", []) self.add_children_gui(self.schema) - self._update_global_values() + self._update_values() self.hierarchical_style_update() def _save(self): @@ -132,7 +132,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = _all_values # Skip first key - all_values = all_values["studio"] + all_values = all_values["system"] # Load studio data with metadata current_configurations = config.studio_configurations() @@ -168,10 +168,10 @@ class SystemWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(origin_values, file_stream, indent=4) - self._update_global_values() + self._update_values() - def _update_global_values(self): - values = {"studio": config.studio_configurations()} + def _update_values(self): + values = {"system": config.studio_configurations()} for input_field in self.input_fields: input_field.update_global_values(values) @@ -408,7 +408,7 @@ class ProjectWidget(QtWidgets.QWidget): self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = self.schema.get("keys", []) self.add_children_gui(self.schema) - self._update_global_values() + self._update_values() self.hierarchical_style_update() def add_children_gui(self, child_configuration): @@ -549,9 +549,9 @@ class ProjectWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(origin_values, file_stream, indent=4) - self._update_global_values() + self._update_values() - def _update_global_values(self): + def _update_values(self): values = {"project": config.global_project_configurations()} for input_field in self.input_fields: input_field.update_global_values(values) From 8ad8b00713e15556b346c75a31b38df2fb18a2f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:55:00 +0200 Subject: [PATCH 782/947] renamed also files and folder from studio to system --- .../0_system_gui_schema.json} | 0 .../1_applications_gui_schema.json | 0 .../{studio_schema => system_schema}/1_intents_gui_schema.json | 0 .../{studio_schema => system_schema}/1_tools_gui_schema.json | 0 .../{studio_schema => system_schema}/1_tray_items.json | 0 pype/tools/config_setting/config_setting/widgets/base.py | 3 ++- 6 files changed, 2 insertions(+), 1 deletion(-) rename pype/tools/config_setting/config_setting/config_gui_schema/{studio_schema/0_studio_gui_schema.json => system_schema/0_system_gui_schema.json} (100%) rename pype/tools/config_setting/config_setting/config_gui_schema/{studio_schema => system_schema}/1_applications_gui_schema.json (100%) rename pype/tools/config_setting/config_setting/config_gui_schema/{studio_schema => system_schema}/1_intents_gui_schema.json (100%) rename pype/tools/config_setting/config_setting/config_gui_schema/{studio_schema => system_schema}/1_tools_gui_schema.json (100%) rename pype/tools/config_setting/config_setting/config_gui_schema/{studio_schema => system_schema}/1_tray_items.json (100%) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/0_studio_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_applications_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_intents_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tools_gui_schema.json rename to pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/studio_schema/1_tray_items.json rename to pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 3d255b416e..e5bbe6d929 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -92,7 +92,7 @@ class SystemWidget(QtWidgets.QWidget): widget.deleteLater() self.input_fields.clear() - self.schema = lib.gui_schema("studio_schema", "0_studio_gui_schema") + self.schema = lib.gui_schema("system_schema", "0_system_gui_schema") self.keys = self.schema.get("keys", []) self.add_children_gui(self.schema) self._update_values() @@ -171,6 +171,7 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() def _update_values(self): + config.default_configuration() values = {"system": config.studio_configurations()} for input_field in self.input_fields: input_field.update_global_values(values) From d2f25b0a0cce496f4fbcbceef2bf4ff30b5fdd58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 10:59:52 +0200 Subject: [PATCH 783/947] is_file removed from schemas --- .../projects_schema/0_project_gui_schema.json | 3 +-- .../1_ftrack_projects_gui_schema.json | 2 -- .../projects_schema/1_plugins_gui_schema.json | 15 ++------------- .../system_schema/0_system_gui_schema.json | 1 - .../system_schema/1_applications_gui_schema.json | 1 - .../system_schema/1_intents_gui_schema.json | 1 - .../system_schema/1_tools_gui_schema.json | 1 - .../system_schema/1_tray_items.json | 1 - 8 files changed, 3 insertions(+), 22 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json index 91bacf2e5a..86504eefd2 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json @@ -4,8 +4,7 @@ "children": [ { "type": "anatomy", - "key": "anatomy", - "is_file": true + "key": "anatomy" }, { "type": "schema", "children": [ diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json index 6608463100..e9937e64b8 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json @@ -10,7 +10,6 @@ "expandable": true, "label": "Status updates", "is_group": true, - "is_file": true, "children": [ { "key": "Ready", @@ -28,7 +27,6 @@ "expandable": true, "label": "Version status to Task status", "is_group": true, - "is_file": true, "children": [ { "key": "in progress", diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 98fbfb206d..302d1dad0a 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -15,7 +15,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [ { "type": "dict", @@ -82,7 +81,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [ { "type": "dict", @@ -125,7 +123,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [ { "type": "dict", @@ -299,7 +296,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [ { "type": "dict", @@ -376,8 +372,7 @@ }, { "type": "raw-json", "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true + "label": "Workfile Build logic" } ] }, { @@ -391,7 +386,6 @@ "expandable": true, "key": "create", "label": "Create plugins", - "is_file": true, "children": [ { "type": "dict", @@ -428,7 +422,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [ { "type": "dict", @@ -551,8 +544,7 @@ }, { "type": "raw-json", "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true + "label": "Workfile Build logic" } ] }, { @@ -566,7 +558,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [ { "type": "dict", @@ -619,7 +610,6 @@ "expandable": true, "key": "create", "label": "Creator plugins", - "is_file": true, "children": [ { "type": "dict", @@ -661,7 +651,6 @@ "expandable": true, "key": "publish", "label": "Publish plugins", - "is_file": true, "children": [] } ] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json index de17328860..0b51267f4e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json @@ -26,7 +26,6 @@ "maximum": 300 }, "is_group": true, - "is_file": true, "key": "templates_mapping", "label": "Muster - Templates mapping" }] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json index bc2c9f239c..af128b138f 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json @@ -3,7 +3,6 @@ "type": "dict", "label": "Applications", "expandable": true, - "is_file": true, "is_group": true, "children": [ { diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json index a4b5e16fa1..11b45c8409 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json @@ -4,7 +4,6 @@ "label": "Intent Setting", "expandable": true, "is_group": true, - "is_file": true, "children": [ { "type": "dict-modifiable", diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json index bf35326d1c..a16f00547e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json @@ -4,7 +4,6 @@ "label": "Tools", "expandable": true, "is_group": true, - "is_file": true, "children": [ { "type": "dict-form", diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json index 13ab0293de..afa509254e 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json @@ -3,7 +3,6 @@ "type": "dict", "label": "Modules", "expandable": true, - "is_file": true, "is_group": true, "children": [ { From 0c256fa30a16934cbc8a08da1841086abe1ca5eb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:00:03 +0200 Subject: [PATCH 784/947] skipped validation of is_file attribute --- .../config_setting/widgets/lib.py | 69 ------------------- 1 file changed, 69 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index 08b0dfc3c4..b32fb734c6 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -81,19 +81,6 @@ def replace_inner_schemas(schema_data, schema_collection): return schema_data -class SchemaMissingFileInfo(Exception): - def __init__(self, invalid): - full_path_keys = [] - for item in invalid: - full_path_keys.append("\"{}\"".format("/".join(item))) - - msg = ( - "Schema has missing definition of output file (\"is_file\" key)" - " for keys. [{}]" - ).format(", ".join(full_path_keys)) - super(SchemaMissingFileInfo, self).__init__(msg) - - class SchemeGroupHierarchyBug(Exception): def __init__(self, invalid): full_path_keys = [] @@ -122,59 +109,6 @@ class SchemaDuplicatedKeys(Exception): super(SchemaDuplicatedKeys, self).__init__(msg) -def file_keys_from_schema(schema_data): - output = [] - keys = [] - key = schema_data.get("key") - if key: - keys.append(key) - - for child in schema_data["children"]: - if child.get("is_file"): - _keys = copy.deepcopy(keys) - _keys.append(child["key"]) - output.append(_keys) - continue - - for result in file_keys_from_schema(child): - _keys = copy.deepcopy(keys) - _keys.extend(result) - output.append(_keys) - return output - - -def validate_all_has_ending_file(schema_data, is_top=True): - if schema_data.get("is_file"): - return None - - children = schema_data.get("children") - if not children: - return [[schema_data["key"]]] - - invalid = [] - keyless = "key" not in schema_data - for child in children: - result = validate_all_has_ending_file(child, False) - if result is None: - continue - - if keyless: - invalid.extend(result) - else: - for item in result: - new_invalid = [schema_data["key"]] - new_invalid.extend(item) - invalid.append(new_invalid) - - if not invalid: - return None - - if not is_top: - return invalid - - raise SchemaMissingFileInfo(invalid) - - def validate_is_group_is_unique_in_hierarchy( schema_data, any_parent_is_group=False, keys=None ): @@ -270,9 +204,6 @@ def validate_keys_are_unique(schema_data, keys=None): def validate_schema(schema_data): - # TODO validator that is_group key is not before is_file child - # TODO validator that is_group or is_file is not on child without key - validate_all_has_ending_file(schema_data) validate_is_group_is_unique_in_hierarchy(schema_data) validate_keys_are_unique(schema_data) From a338b8c04e0e8c5445d1f0ca4533e7e97a66b8e4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:20:34 +0200 Subject: [PATCH 785/947] renamed function for inner schemas --- pype/tools/config_setting/config_setting/widgets/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index b32fb734c6..69c3259b3b 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -55,7 +55,7 @@ def convert_overrides_to_gui_data(data, first=True): return output -def replace_inner_schemas(schema_data, schema_collection): +def _fill_inner_schemas(schema_data, schema_collection): if schema_data["type"] == "schema": raise ValueError("First item in schema data can't be schema.") @@ -66,12 +66,12 @@ def replace_inner_schemas(schema_data, schema_collection): new_children = [] for child in children: if child["type"] != "schema": - new_child = replace_inner_schemas(child, schema_collection) + new_child = _fill_inner_schemas(child, schema_collection) new_children.append(new_child) continue for schema_name in child["children"]: - new_child = replace_inner_schemas( + new_child = _fill_inner_schemas( schema_collection[schema_name], schema_collection ) @@ -227,7 +227,7 @@ def gui_schema(subfolder, main_schema_name): schema_data = json.load(json_stream) loaded_schemas[basename] = schema_data - main_schema = replace_inner_schemas( + main_schema = _fill_inner_schemas( loaded_schemas[main_schema_name], loaded_schemas ) From afcd282ada6c5c8e3c0f1abe50bd800c04cad0ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:20:50 +0200 Subject: [PATCH 786/947] ConfigObject has is from defaults --- pype/tools/config_setting/config_setting/widgets/inputs.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index f975567a6a..fed9c6b5c7 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -16,6 +16,7 @@ class ConfigObject(AbstractConfigObject): default_state = "" + _is_from_defaults = True _as_widget = False _is_overriden = False _is_modified = False @@ -32,6 +33,10 @@ class ConfigObject(AbstractConfigObject): self._log = logging.getLogger(self.__class__.__name__) return self._log + @property + def is_from_defaults(self): + return self._is_from_defaults + @property def is_modified(self): """Has object any changes that require saving.""" From 7198576e59c0f3ca487f3972a78f017024d8b724 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:39:02 +0200 Subject: [PATCH 787/947] reverse logic of defaults, better is to know if has studi ooverrides --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index fed9c6b5c7..14cf573398 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -16,7 +16,7 @@ class ConfigObject(AbstractConfigObject): default_state = "" - _is_from_defaults = True + _has_studio_override = True _as_widget = False _is_overriden = False _is_modified = False @@ -34,8 +34,8 @@ class ConfigObject(AbstractConfigObject): return self._log @property - def is_from_defaults(self): - return self._is_from_defaults + def has_studio_override(self): + return self._has_studio_override @property def is_modified(self): From 918d592777d41a5e87617acd5562cb4b3a80fcf1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:42:08 +0200 Subject: [PATCH 788/947] renamed global_value to studio_value --- .../config_setting/widgets/anatomy_inputs.py | 20 ++-- .../config_setting/widgets/base.py | 4 +- .../config_setting/widgets/inputs.py | 106 +++++++++--------- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index e59de3980f..957691cbdc 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -75,7 +75,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.value_changed.connect(self._on_value_change) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): self._state = None self._child_state = None @@ -84,8 +84,8 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): else: value = NOT_SET - self.root_widget.update_global_values(value) - self.templates_widget.update_global_values(value) + self.root_widget.update_studio_values(value) + self.templates_widget.update_studio_values(value) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -258,7 +258,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def is_multiroot(self): return self.multiroot_checkbox.isChecked() - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): self._state = None self._multiroot_state = None @@ -279,11 +279,11 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(is_multiroot) if is_multiroot: - self.singleroot_widget.update_global_values(NOT_SET) - self.multiroot_widget.update_global_values(value) + self.singleroot_widget.update_studio_values(NOT_SET) + self.multiroot_widget.update_studio_values(value) else: - self.singleroot_widget.update_global_values(value) - self.multiroot_widget.update_global_values(NOT_SET) + self.singleroot_widget.update_studio_values(value) + self.multiroot_widget.update_studio_values(NOT_SET) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -489,9 +489,9 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): layout.addWidget(body_widget) - def update_global_values(self, values): + def update_studio_values(self, values): self._state = None - self.value_input.update_global_values(values) + self.value_input.update_studio_values(values) def apply_overrides(self, parent_values): self._state = None diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index e5bbe6d929..82a5d024b0 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -174,7 +174,7 @@ class SystemWidget(QtWidgets.QWidget): config.default_configuration() values = {"system": config.studio_configurations()} for input_field in self.input_fields: - input_field.update_global_values(values) + input_field.update_studio_values(values) for input_field in self.input_fields: input_field.hierarchical_style_update() @@ -555,7 +555,7 @@ class ProjectWidget(QtWidgets.QWidget): def _update_values(self): values = {"project": config.global_project_configurations()} for input_field in self.input_fields: - input_field.update_global_values(values) + input_field.update_studio_values(values) for input_field in self.input_fields: input_field.hierarchical_style_update() diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 14cf573398..e70b2a9b3a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -221,7 +221,7 @@ class InputObject(ConfigObject): self.set_value(self.start_value) if not self.is_overidable: - self._is_modified = self.global_value != self.item_value() + self._is_modified = self.studio_value != self.item_value() self._is_overriden = False return @@ -273,7 +273,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -300,7 +300,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.checkbox.stateChanged.connect(self._on_value_change) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if self._as_widget: value = parent_values @@ -313,10 +313,10 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): elif self.default_value is not NOT_SET: self.set_value(self.default_value) - self.global_value = value + self.studio_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def set_value(self, value): # Ignore value change because if `self.isChecked()` has same @@ -338,7 +338,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -385,7 +385,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -413,7 +413,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.input_field.valueChanged.connect(self._on_value_change) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if self._as_widget: value = parent_values @@ -427,10 +427,10 @@ class NumberWidget(QtWidgets.QWidget, InputObject): elif self.default_value is not NOT_SET: self.set_value(self.default_value) - self.global_value = value + self.studio_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def set_value(self, value): self.input_field.setValue(value) @@ -453,7 +453,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -502,7 +502,7 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.multiline = input_data.get("multiline", False) self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -528,7 +528,7 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.text_input.textChanged.connect(self._on_value_change) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if self._as_widget: value = parent_values @@ -541,10 +541,10 @@ class TextWidget(QtWidgets.QWidget, InputObject): elif self.default_value is not NOT_SET: self.set_value(self.default_value) - self.global_value = value + self.studio_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def set_value(self, value): if self.multiline: @@ -570,7 +570,7 @@ class TextWidget(QtWidgets.QWidget, InputObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -620,7 +620,7 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -641,7 +641,7 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self.path_input.textChanged.connect(self._on_value_change) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if self._as_widget: value = parent_values @@ -654,10 +654,10 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): elif self.default_value is not NOT_SET: self.set_value(self.default_value) - self.global_value = value + self.studio_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def set_value(self, value): self.path_input.setText(value) @@ -684,7 +684,7 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): elif self.is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -789,7 +789,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QVBoxLayout(self) @@ -815,7 +815,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.text_input.textChanged.connect(self._on_value_change) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if self._as_widget: value = parent_values @@ -830,10 +830,10 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._is_invalid = self.text_input.has_invalid_value() - self.global_value = value + self.studio_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def set_value(self, value): self.text_input.set_value(value) @@ -857,7 +857,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -992,7 +992,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.input_modifiers = input_data.get("input_modifiers") or {} self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET self.key = input_data["key"] @@ -1031,7 +1031,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): def clear_value(self): self.set_value([]) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): old_inputs = tuple(self.input_fields) value = NOT_SET @@ -1040,7 +1040,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) - self.global_value = value + self.studio_value = value if value is not NOT_SET: for item_value in value: @@ -1058,7 +1058,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value self.hierarchical_style_update() def set_value(self, value): @@ -1081,7 +1081,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -1114,7 +1114,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if value is not None: - item_widget.value_input.update_global_values(value) + item_widget.value_input.update_studio_values(value) else: self._on_value_change() self.updateGeometry() @@ -1248,10 +1248,10 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.update_style() self.value_changed.emit(self) - def update_global_values(self, key, value): + def update_studio_values(self, key, value): self.origin_key = key self.key_input.setText(key) - self.value_input.update_global_values(value) + self.value_input.update_studio_values(value) def apply_overrides(self, key, value): self.origin_key = key @@ -1341,7 +1341,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self._as_widget = as_widget self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET any_parent_is_group = parent.is_group @@ -1397,7 +1397,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def count(self): return len(self.input_fields) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): old_inputs = tuple(self.input_fields) value = NOT_SET @@ -1406,7 +1406,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) - self.global_value = value + self.studio_value = value if value is not NOT_SET: for item_key, item_value in value.items(): @@ -1424,7 +1424,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def set_value(self, value): previous_inputs = tuple(self.input_fields) @@ -1462,7 +1462,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.update_style() @@ -1534,7 +1534,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if self._is_overriden: item_widget.apply_overrides(key, value) else: - item_widget.update_global_values(key, value) + item_widget.update_studio_values(key, value) self.hierarchical_style_update() else: self._on_value_change() @@ -1699,13 +1699,13 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) for item in self.input_fields: - item.update_global_values(value) + item.update_studio_values(value) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -1965,13 +1965,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) for item in self.input_fields: - item.update_global_values(value) + item.update_studio_values(value) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -2062,7 +2062,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.multipath = input_data.get("multipath", False) self.override_value = NOT_SET - self.global_value = NOT_SET + self.studio_value = NOT_SET self.start_value = NOT_SET self.input_fields = [] @@ -2140,7 +2140,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.setFocusProxy(self.input_fields[0]) self.content_layout.addWidget(proxy_widget) - def update_global_values(self, parent_values): + def update_studio_values(self, parent_values): value = NOT_SET if self._as_widget: value = parent_values @@ -2148,15 +2148,15 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): value = parent_values.get(self.key, NOT_SET) if not self.multiplatform: - self.input_fields[0].update_global_values(parent_values) + self.input_fields[0].update_studio_values(parent_values) elif self.multiplatform: for input_field in self.input_fields: - input_field.update_global_values(value) + input_field.update_studio_values(value) - self.global_value = value + self.studio_value = value self.start_value = self.item_value() - self._is_modified = self.global_value != self.start_value + self._is_modified = self.studio_value != self.start_value def apply_overrides(self, parent_values): self._is_modified = False @@ -2215,7 +2215,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): elif self._is_overriden: self._is_modified = self.item_value() != self.override_value else: - self._is_modified = self.item_value() != self.global_value + self._is_modified = self.item_value() != self.studio_value self.hierarchical_style_update() @@ -2408,9 +2408,9 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() - def update_global_values(self, value): + def update_studio_values(self, value): for item in self.input_fields: - item.update_global_values(value) + item.update_studio_values(value) def _on_value_change(self, item=None): if self.ignore_value_changes: From 4d44eda38816b874bdb81581163dca28006d668c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:47:53 +0200 Subject: [PATCH 789/947] added default_value next to studio_value and override_value --- .../config_setting/widgets/inputs.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e70b2a9b3a..5ad97fb698 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -272,8 +272,9 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -384,8 +385,9 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -501,8 +503,9 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.multiline = input_data.get("multiline", False) - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -619,8 +622,9 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) @@ -788,8 +792,9 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.default_value = input_data.get("default", NOT_SET) - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET layout = QtWidgets.QVBoxLayout(self) @@ -991,8 +996,9 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET self.key = input_data["key"] @@ -1340,8 +1346,9 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self._state = None self._as_widget = as_widget - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET any_parent_is_group = parent.is_group @@ -2061,8 +2068,9 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.multiplatform = input_data.get("multiplatform", False) self.multipath = input_data.get("multipath", False) - self.override_value = NOT_SET + self.default_value = NOT_SET self.studio_value = NOT_SET + self.override_value = NOT_SET self.start_value = NOT_SET self.input_fields = [] From 782424139648021798517cd24a5949a7f292d1ba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:54:59 +0200 Subject: [PATCH 790/947] removed reset_value method --- .../config_setting/widgets/inputs.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 5ad97fb698..54b7bf96e4 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -440,9 +440,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): def clear_value(self): self.set_value(0) - def reset_value(self): - self.set_value(self.start_value) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -555,9 +552,6 @@ class TextWidget(QtWidgets.QWidget, InputObject): else: self.text_input.setText(value) - def reset_value(self): - self.set_value(self.start_value) - def clear_value(self): self.set_value("") @@ -666,9 +660,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.path_input.setText(value) - def reset_value(self): - self.set_value(self.start_value) - def clear_value(self): self.set_value("") @@ -843,9 +834,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.text_input.set_value(value) - def reset_value(self): - self.set_value(self.start_value) - def clear_value(self): self.set_value("") @@ -1031,9 +1019,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): def count(self): return len(self.input_fields) - def reset_value(self): - self.set_value(self.start_value) - def clear_value(self): self.set_value([]) @@ -2203,10 +2188,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): _value = value[input_field.key] input_field.set_value(_value) - def reset_value(self): - for input_field in self.input_fields: - input_field.reset_value() - def clear_value(self): for input_field in self.input_fields: input_field.clear_value() From f263a71e6b02bd25ea804b741f70d2b7a8aef292 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 11:56:24 +0200 Subject: [PATCH 791/947] removed clear value --- .../config_setting/widgets/anatomy_inputs.py | 3 --- .../config_setting/widgets/inputs.py | 22 ------------------- 2 files changed, 25 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 957691cbdc..fbc3a3a2ed 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -102,9 +102,6 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): def set_value(self, value): raise TypeError("AnatomyWidget does not allow to use `set_value`") - def clear_value(self): - raise TypeError("AnatomyWidget does not allow to use `clear_value`") - def _on_value_change(self, item=None): if self.ignore_value_changes: return diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 54b7bf96e4..40de1c1347 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -324,9 +324,6 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): # value as `value` the `_on_value_change` is not triggered self.checkbox.setChecked(value) - def clear_value(self): - self.set_value(False) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -437,9 +434,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.input_field.setValue(value) - def clear_value(self): - self.set_value(0) - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -552,9 +546,6 @@ class TextWidget(QtWidgets.QWidget, InputObject): else: self.text_input.setText(value) - def clear_value(self): - self.set_value("") - def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -660,9 +651,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.path_input.setText(value) - def clear_value(self): - self.set_value("") - def focusOutEvent(self, event): self.path_input.clear_end_path() super(PathInput, self).focusOutEvent(event) @@ -834,9 +822,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.text_input.set_value(value) - def clear_value(self): - self.set_value("") - def _on_value_change(self, item=None): self._is_invalid = self.text_input.has_invalid_value() if self.ignore_value_changes: @@ -1019,9 +1004,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): def count(self): return len(self.input_fields) - def clear_value(self): - self.set_value([]) - def update_studio_values(self, parent_values): old_inputs = tuple(self.input_fields) @@ -2188,10 +2170,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): _value = value[input_field.key] input_field.set_value(_value) - def clear_value(self): - for input_field in self.input_fields: - input_field.clear_value() - def _on_value_change(self, item=None): if self.ignore_value_changes: return From 9924b2899e39a4c9c6d839430076184c865cdd22 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:06:19 +0200 Subject: [PATCH 792/947] removed default from schemas and laoding from schemas --- .../projects_schema/1_plugins_gui_schema.json | 120 ++++++------------ .../config_setting/widgets/inputs.py | 8 -- 2 files changed, 40 insertions(+), 88 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 302d1dad0a..597f51d6fd 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -27,41 +27,34 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": true + "label": "Enabled" }, { "type": "dict-form", "children": [ { "type": "text", "key": "deadline_department", - "label": "Deadline apartment", - "default": "" + "label": "Deadline apartment" }, { "type": "number", "key": "deadline_priority", - "label": "Deadline priority", - "default": 50 + "label": "Deadline priority" }, { "type": "text", "key": "deadline_pool", - "label": "Deadline pool", - "default": "" + "label": "Deadline pool" }, { "type": "text", "key": "deadline_pool_secondary", - "label": "Deadline pool (secondary)", - "default": "" + "label": "Deadline pool (secondary)" }, { "type": "text", "key": "deadline_group", - "label": "Deadline Group", - "default": "" + "label": "Deadline Group" }, { "type": "number", "key": "deadline_chunk_size", - "label": "Deadline Chunk size", - "default": 10 + "label": "Deadline Chunk size" } ] } @@ -93,19 +86,16 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": false + "label": "Enabled" }, { "type": "text", "key": "note_with_intent_template", - "label": "Note with intent template", - "default": "{intent}: {comment}" + "label": "Note with intent template" }, { "type": "list", "object_type": "text", "key": "note_labels", - "label": "Note labels", - "default": [] + "label": "Note labels" } ] } @@ -154,14 +144,12 @@ "type": "list", "object_type": "text", "key": "input", - "label": "FFmpeg input arguments", - "default": [] + "label": "FFmpeg input arguments" }, { "type": "list", "object_type": "text", "key": "output", - "label": "FFmpeg output arguments", - "default": [] + "label": "FFmpeg output arguments" } ] } @@ -177,13 +165,11 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": true + "label": "Enabled" }, { "type": "raw-json", "key": "profiles", - "label": "Profiles", - "default": [] + "label": "Profiles" } ] }, { @@ -207,33 +193,27 @@ { "type": "number", "key": "font_size", - "label": "Font size", - "default": 42 + "label": "Font size" }, { "type": "number", "key": "opacity", - "label": "Font opacity", - "default": 1 + "label": "Font opacity" }, { "type": "number", "key": "bg_opacity", - "label": "Background opacity", - "default": 1 + "label": "Background opacity" }, { "type": "number", "key": "x_offset", - "label": "X Offset", - "default": 5 + "label": "X Offset" }, { "type": "number", "key": "y_offset", - "label": "Y Offset", - "default": 5 + "label": "Y Offset" }, { "type": "number", "key": "bg_padding", - "label": "Padding aroung text", - "default": 5 + "label": "Padding aroung text" } ] }, { @@ -316,8 +296,7 @@ }, { "type": "text", "key": "regex", - "label": "Validation regex", - "default": "(.*)_(\\d)*_(?P.*)_(GEO)" + "label": "Validation regex" } ] }, { @@ -349,8 +328,7 @@ }, { "type": "text", "key": "regex", - "label": "Validation regex", - "default": "(?P.*)_(.*)_SHD" + "label": "Validation regex" } ] }, { @@ -397,8 +375,7 @@ { "type": "text", "key": "fpath_template", - "label": "Path template", - "default": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + "label": "Path template" } ] }, { @@ -411,8 +388,7 @@ { "type": "text", "key": "fpath_template", - "label": "Path template", - "default": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + "label": "Path template" } ] } @@ -434,8 +410,7 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": true + "label": "Enabled" }, { "type": "raw-json", "key": "nodes", @@ -453,8 +428,7 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": false + "label": "Enabled" }, { "type": "raw-json", "key": "knobs", @@ -472,8 +446,7 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": false + "label": "Enabled" } ] }, { @@ -487,13 +460,11 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": true + "label": "Enabled" }, { "type": "boolean", "key": "viewer_lut_raw", - "label": "Viewer LUT raw", - "default": false + "label": "Viewer LUT raw" } ] }, { @@ -506,8 +477,7 @@ { "type": "boolean", "key": "viewer_lut_raw", - "label": "Viewer LUT raw", - "default": false + "label": "Viewer LUT raw" } ] }, { @@ -520,23 +490,19 @@ { "type": "number", "key": "deadline_priority", - "label": "deadline_priority", - "default": 50 + "label": "deadline_priority" }, { "type": "text", "key": "deadline_pool", - "label": "deadline_pool", - "default": "" + "label": "deadline_pool" }, { "type": "text", "key": "deadline_pool_secondary", - "label": "deadline_pool_secondary", - "default": "" + "label": "deadline_pool_secondary" }, { "type": "number", "key": "deadline_chunk_size", - "label": "deadline_chunk_size", - "default": 1 + "label": "deadline_chunk_size" } ] } @@ -570,8 +536,7 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": false + "label": "Enabled" } ] }, { @@ -585,14 +550,12 @@ { "type": "boolean", "key": "enabled", - "label": "Enabled", - "default": true + "label": "Enabled" }, { "type": "list", "object_type": "text", "key": "tags_addition", - "label": "Tags addition", - "default": [] + "label": "Tags addition" } ] } @@ -621,18 +584,15 @@ { "type": "text", "key": "clipName", - "label": "Clip name template", - "default": "{track}{sequence}{shot}" + "label": "Clip name template" }, { "type": "text", "key": "folder", - "label": "Folder", - "default": "takes" + "label": "Folder" }, { "type": "number", "key": "steps", - "label": "Steps", - "default": 20 + "label": "Steps" } ] } diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 40de1c1347..16ecd5cd1a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -270,7 +270,6 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) self.default_value = NOT_SET self.studio_value = NOT_SET @@ -380,7 +379,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) self.default_value = NOT_SET self.studio_value = NOT_SET @@ -490,7 +488,6 @@ class TextWidget(QtWidgets.QWidget, InputObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) self.multiline = input_data.get("multiline", False) @@ -605,7 +602,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) self.default_value = NOT_SET self.studio_value = NOT_SET @@ -769,7 +765,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) self.default_value = NOT_SET self.studio_value = NOT_SET @@ -966,7 +961,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): self._is_nullable = input_data.get("is_nullable", False) self.object_type = input_data["object_type"] - self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} self.default_value = NOT_SET @@ -1363,7 +1357,6 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.object_type = input_data["object_type"] - self.default_value = input_data.get("default", NOT_SET) self.input_modifiers = input_data.get("input_modifiers") or {} self.add_row(is_empty=True) @@ -2031,7 +2024,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self._is_group = False self._is_nullable = input_data.get("is_nullable", False) - self.default_value = input_data.get("default", NOT_SET) self.multiplatform = input_data.get("multiplatform", False) self.multipath = input_data.get("multipath", False) From 022214a992e0468d16b1f73c41ac2da618b4aeb5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:08:48 +0200 Subject: [PATCH 793/947] has studi ooverrides is False by default --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 16ecd5cd1a..057ed3b584 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -16,7 +16,7 @@ class ConfigObject(AbstractConfigObject): default_state = "" - _has_studio_override = True + _has_studio_override = False _as_widget = False _is_overriden = False _is_modified = False From ef2b0c955e5c7466a45ebf3c58ff8e32e924c212 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:12:08 +0200 Subject: [PATCH 794/947] studio_configuration folder renamed to system_configuration --- .../environments/avalon.json | 0 .../environments/blender.json | 0 .../environments/celaction.json | 0 .../environments/deadline.json | 0 .../environments/ftrack.json | 0 .../environments/global.json | 0 .../environments/harmony.json | 0 .../environments/houdini.json | 0 .../environments/maya.json | 0 .../environments/maya_2018.json | 0 .../environments/maya_2020.json | 0 .../environments/mayabatch.json | 0 .../environments/mayabatch_2019.json | 0 .../environments/mtoa_3.1.1.json | 0 .../environments/muster.json | 0 .../environments/nuke.json | 0 .../environments/nukestudio.json | 0 .../environments/nukestudio_10.0.json | 0 .../environments/nukex.json | 0 .../environments/nukex_10.0.json | 0 .../environments/photoshop.json | 0 .../environments/premiere.json | 0 .../environments/resolve.json | 0 .../environments/storyboardpro.json | 0 .../environments/unreal_4.24.json | 0 .../environments/vray_4300.json | 0 .../global/applications.json | 0 .../global/intent.json | 0 .../global/tools.json | 0 .../global/tray_modules.json | 0 .../launchers/blender_2.80.toml | 0 .../launchers/blender_2.81.toml | 0 .../launchers/blender_2.82.toml | 0 .../launchers/blender_2.83.toml | 0 .../launchers/celaction_local.toml | 0 .../launchers/celaction_publish.toml | 0 .../launchers/darwin/blender_2.82 | 0 .../launchers/darwin/harmony_17 | 0 .../launchers/darwin/harmony_17_launch | 0 .../launchers/darwin/python3 | 0 .../launchers/harmony_17.toml | 0 .../launchers/houdini_16.toml | 0 .../launchers/houdini_17.toml | 0 .../launchers/houdini_18.toml | 0 .../launchers/linux/maya2016 | 0 .../launchers/linux/maya2017 | 0 .../launchers/linux/maya2018 | 0 .../launchers/linux/maya2019 | 0 .../launchers/linux/maya2020 | 0 .../launchers/linux/nuke11.3 | 0 .../launchers/linux/nuke12.0 | 0 .../launchers/linux/nukestudio11.3 | 0 .../launchers/linux/nukestudio12.0 | 0 .../launchers/linux/nukex11.3 | 0 .../launchers/linux/nukex12.0 | 0 .../launchers/maya_2016.toml | 0 .../launchers/maya_2017.toml | 0 .../launchers/maya_2018.toml | 0 .../launchers/maya_2019.toml | 0 .../launchers/maya_2020.toml | 0 .../launchers/mayabatch_2019.toml | 0 .../launchers/mayabatch_2020.toml | 0 .../launchers/mayapy2016.toml | 0 .../launchers/mayapy2017.toml | 0 .../launchers/mayapy2018.toml | 0 .../launchers/mayapy2019.toml | 0 .../launchers/mayapy2020.toml | 0 .../launchers/myapp.toml | 0 .../launchers/nuke_10.0.toml | 0 .../launchers/nuke_11.0.toml | 0 .../launchers/nuke_11.2.toml | 0 .../launchers/nuke_11.3.toml | 0 .../launchers/nuke_12.0.toml | 0 .../launchers/nukestudio_10.0.toml | 0 .../launchers/nukestudio_11.0.toml | 0 .../launchers/nukestudio_11.2.toml | 0 .../launchers/nukestudio_11.3.toml | 0 .../launchers/nukestudio_12.0.toml | 0 .../launchers/nukex_10.0.toml | 0 .../launchers/nukex_11.0.toml | 0 .../launchers/nukex_11.2.toml | 0 .../launchers/nukex_11.3.toml | 0 .../launchers/nukex_12.0.toml | 0 .../launchers/photoshop_2020.toml | 0 .../launchers/premiere_2019.toml | 0 .../launchers/premiere_2020.toml | 0 .../launchers/python_2.toml | 0 .../launchers/python_3.toml | 0 .../launchers/resolve_16.toml | 0 .../launchers/shell.toml | 0 .../launchers/storyboardpro_7.toml | 0 .../launchers/unreal_4.24.toml | 0 .../launchers/windows/blender_2.80.bat | 0 .../launchers/windows/blender_2.81.bat | 0 .../launchers/windows/blender_2.82.bat | 0 .../launchers/windows/blender_2.83.bat | 0 .../launchers/windows/celaction_local.bat | 0 .../launchers/windows/celaction_publish.bat | 0 .../launchers/windows/harmony_17.bat | 0 .../launchers/windows/houdini_16.bat | 0 .../launchers/windows/houdini_17.bat | 0 .../launchers/windows/houdini_18.bat | 0 .../launchers/windows/maya2016.bat | 0 .../launchers/windows/maya2017.bat | 0 .../launchers/windows/maya2018.bat | 0 .../launchers/windows/maya2019.bat | 0 .../launchers/windows/maya2020.bat | 0 .../launchers/windows/mayabatch2019.bat | 0 .../launchers/windows/mayabatch2020.bat | 0 .../launchers/windows/mayapy2016.bat | 0 .../launchers/windows/mayapy2017.bat | 0 .../launchers/windows/mayapy2018.bat | 0 .../launchers/windows/mayapy2019.bat | 0 .../launchers/windows/mayapy2020.bat | 0 .../launchers/windows/nuke10.0.bat | 0 .../launchers/windows/nuke11.0.bat | 0 .../launchers/windows/nuke11.2.bat | 0 .../launchers/windows/nuke11.3.bat | 0 .../launchers/windows/nuke12.0.bat | 0 .../launchers/windows/nukestudio10.0.bat | 0 .../launchers/windows/nukestudio11.0.bat | 0 .../launchers/windows/nukestudio11.2.bat | 0 .../launchers/windows/nukestudio11.3.bat | 0 .../launchers/windows/nukestudio12.0.bat | 0 .../launchers/windows/nukex10.0.bat | 0 .../launchers/windows/nukex11.0.bat | 0 .../launchers/windows/nukex11.2.bat | 0 .../launchers/windows/nukex11.3.bat | 0 .../launchers/windows/nukex12.0.bat | 0 .../launchers/windows/photoshop_2020.bat | 0 .../launchers/windows/premiere_pro_2019.bat | 0 .../launchers/windows/premiere_pro_2020.bat | 0 .../launchers/windows/python3.bat | 0 .../launchers/windows/resolve_16.bat | 0 .../launchers/windows/shell.bat | 0 .../launchers/windows/storyboardpro_7.bat | 0 .../launchers/windows/unreal.bat | 0 .../muster/templates_mapping.json | 0 .../standalone_publish/families.json | 0 139 files changed, 0 insertions(+), 0 deletions(-) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/avalon.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/blender.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/celaction.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/deadline.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/ftrack.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/global.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/harmony.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/houdini.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/maya.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/maya_2018.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/maya_2020.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/mayabatch.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/mayabatch_2019.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/mtoa_3.1.1.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/muster.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/nuke.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/nukestudio.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/nukestudio_10.0.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/nukex.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/nukex_10.0.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/photoshop.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/premiere.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/resolve.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/storyboardpro.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/unreal_4.24.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/environments/vray_4300.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/global/applications.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/global/intent.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/global/tools.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/global/tray_modules.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/blender_2.80.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/blender_2.81.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/blender_2.82.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/blender_2.83.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/celaction_local.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/celaction_publish.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/darwin/blender_2.82 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/darwin/harmony_17 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/darwin/harmony_17_launch (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/darwin/python3 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/harmony_17.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/houdini_16.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/houdini_17.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/houdini_18.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/maya2016 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/maya2017 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/maya2018 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/maya2019 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/maya2020 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/nuke11.3 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/nuke12.0 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/nukestudio11.3 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/nukestudio12.0 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/nukex11.3 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/linux/nukex12.0 (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/maya_2016.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/maya_2017.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/maya_2018.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/maya_2019.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/maya_2020.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayabatch_2019.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayabatch_2020.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayapy2016.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayapy2017.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayapy2018.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayapy2019.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/mayapy2020.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/myapp.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nuke_10.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nuke_11.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nuke_11.2.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nuke_11.3.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nuke_12.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukestudio_10.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukestudio_11.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukestudio_11.2.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukestudio_11.3.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukestudio_12.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukex_10.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukex_11.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukex_11.2.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukex_11.3.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/nukex_12.0.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/photoshop_2020.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/premiere_2019.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/premiere_2020.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/python_2.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/python_3.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/resolve_16.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/shell.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/storyboardpro_7.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/unreal_4.24.toml (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/blender_2.80.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/blender_2.81.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/blender_2.82.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/blender_2.83.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/celaction_local.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/celaction_publish.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/harmony_17.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/houdini_16.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/houdini_17.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/houdini_18.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/maya2016.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/maya2017.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/maya2018.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/maya2019.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/maya2020.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayabatch2019.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayabatch2020.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayapy2016.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayapy2017.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayapy2018.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayapy2019.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/mayapy2020.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nuke10.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nuke11.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nuke11.2.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nuke11.3.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nuke12.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukestudio10.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukestudio11.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukestudio11.2.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukestudio11.3.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukestudio12.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukex10.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukex11.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukex11.2.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukex11.3.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/nukex12.0.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/photoshop_2020.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/premiere_pro_2019.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/premiere_pro_2020.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/python3.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/resolve_16.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/shell.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/storyboardpro_7.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/launchers/windows/unreal.bat (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/muster/templates_mapping.json (100%) rename pype/configurations/defaults/{studio_configurations => system_configurations}/standalone_publish/families.json (100%) diff --git a/pype/configurations/defaults/studio_configurations/environments/avalon.json b/pype/configurations/defaults/system_configurations/environments/avalon.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/avalon.json rename to pype/configurations/defaults/system_configurations/environments/avalon.json diff --git a/pype/configurations/defaults/studio_configurations/environments/blender.json b/pype/configurations/defaults/system_configurations/environments/blender.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/blender.json rename to pype/configurations/defaults/system_configurations/environments/blender.json diff --git a/pype/configurations/defaults/studio_configurations/environments/celaction.json b/pype/configurations/defaults/system_configurations/environments/celaction.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/celaction.json rename to pype/configurations/defaults/system_configurations/environments/celaction.json diff --git a/pype/configurations/defaults/studio_configurations/environments/deadline.json b/pype/configurations/defaults/system_configurations/environments/deadline.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/deadline.json rename to pype/configurations/defaults/system_configurations/environments/deadline.json diff --git a/pype/configurations/defaults/studio_configurations/environments/ftrack.json b/pype/configurations/defaults/system_configurations/environments/ftrack.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/ftrack.json rename to pype/configurations/defaults/system_configurations/environments/ftrack.json diff --git a/pype/configurations/defaults/studio_configurations/environments/global.json b/pype/configurations/defaults/system_configurations/environments/global.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/global.json rename to pype/configurations/defaults/system_configurations/environments/global.json diff --git a/pype/configurations/defaults/studio_configurations/environments/harmony.json b/pype/configurations/defaults/system_configurations/environments/harmony.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/harmony.json rename to pype/configurations/defaults/system_configurations/environments/harmony.json diff --git a/pype/configurations/defaults/studio_configurations/environments/houdini.json b/pype/configurations/defaults/system_configurations/environments/houdini.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/houdini.json rename to pype/configurations/defaults/system_configurations/environments/houdini.json diff --git a/pype/configurations/defaults/studio_configurations/environments/maya.json b/pype/configurations/defaults/system_configurations/environments/maya.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/maya.json rename to pype/configurations/defaults/system_configurations/environments/maya.json diff --git a/pype/configurations/defaults/studio_configurations/environments/maya_2018.json b/pype/configurations/defaults/system_configurations/environments/maya_2018.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/maya_2018.json rename to pype/configurations/defaults/system_configurations/environments/maya_2018.json diff --git a/pype/configurations/defaults/studio_configurations/environments/maya_2020.json b/pype/configurations/defaults/system_configurations/environments/maya_2020.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/maya_2020.json rename to pype/configurations/defaults/system_configurations/environments/maya_2020.json diff --git a/pype/configurations/defaults/studio_configurations/environments/mayabatch.json b/pype/configurations/defaults/system_configurations/environments/mayabatch.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/mayabatch.json rename to pype/configurations/defaults/system_configurations/environments/mayabatch.json diff --git a/pype/configurations/defaults/studio_configurations/environments/mayabatch_2019.json b/pype/configurations/defaults/system_configurations/environments/mayabatch_2019.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/mayabatch_2019.json rename to pype/configurations/defaults/system_configurations/environments/mayabatch_2019.json diff --git a/pype/configurations/defaults/studio_configurations/environments/mtoa_3.1.1.json b/pype/configurations/defaults/system_configurations/environments/mtoa_3.1.1.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/mtoa_3.1.1.json rename to pype/configurations/defaults/system_configurations/environments/mtoa_3.1.1.json diff --git a/pype/configurations/defaults/studio_configurations/environments/muster.json b/pype/configurations/defaults/system_configurations/environments/muster.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/muster.json rename to pype/configurations/defaults/system_configurations/environments/muster.json diff --git a/pype/configurations/defaults/studio_configurations/environments/nuke.json b/pype/configurations/defaults/system_configurations/environments/nuke.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/nuke.json rename to pype/configurations/defaults/system_configurations/environments/nuke.json diff --git a/pype/configurations/defaults/studio_configurations/environments/nukestudio.json b/pype/configurations/defaults/system_configurations/environments/nukestudio.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/nukestudio.json rename to pype/configurations/defaults/system_configurations/environments/nukestudio.json diff --git a/pype/configurations/defaults/studio_configurations/environments/nukestudio_10.0.json b/pype/configurations/defaults/system_configurations/environments/nukestudio_10.0.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/nukestudio_10.0.json rename to pype/configurations/defaults/system_configurations/environments/nukestudio_10.0.json diff --git a/pype/configurations/defaults/studio_configurations/environments/nukex.json b/pype/configurations/defaults/system_configurations/environments/nukex.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/nukex.json rename to pype/configurations/defaults/system_configurations/environments/nukex.json diff --git a/pype/configurations/defaults/studio_configurations/environments/nukex_10.0.json b/pype/configurations/defaults/system_configurations/environments/nukex_10.0.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/nukex_10.0.json rename to pype/configurations/defaults/system_configurations/environments/nukex_10.0.json diff --git a/pype/configurations/defaults/studio_configurations/environments/photoshop.json b/pype/configurations/defaults/system_configurations/environments/photoshop.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/photoshop.json rename to pype/configurations/defaults/system_configurations/environments/photoshop.json diff --git a/pype/configurations/defaults/studio_configurations/environments/premiere.json b/pype/configurations/defaults/system_configurations/environments/premiere.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/premiere.json rename to pype/configurations/defaults/system_configurations/environments/premiere.json diff --git a/pype/configurations/defaults/studio_configurations/environments/resolve.json b/pype/configurations/defaults/system_configurations/environments/resolve.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/resolve.json rename to pype/configurations/defaults/system_configurations/environments/resolve.json diff --git a/pype/configurations/defaults/studio_configurations/environments/storyboardpro.json b/pype/configurations/defaults/system_configurations/environments/storyboardpro.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/storyboardpro.json rename to pype/configurations/defaults/system_configurations/environments/storyboardpro.json diff --git a/pype/configurations/defaults/studio_configurations/environments/unreal_4.24.json b/pype/configurations/defaults/system_configurations/environments/unreal_4.24.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/unreal_4.24.json rename to pype/configurations/defaults/system_configurations/environments/unreal_4.24.json diff --git a/pype/configurations/defaults/studio_configurations/environments/vray_4300.json b/pype/configurations/defaults/system_configurations/environments/vray_4300.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/environments/vray_4300.json rename to pype/configurations/defaults/system_configurations/environments/vray_4300.json diff --git a/pype/configurations/defaults/studio_configurations/global/applications.json b/pype/configurations/defaults/system_configurations/global/applications.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/global/applications.json rename to pype/configurations/defaults/system_configurations/global/applications.json diff --git a/pype/configurations/defaults/studio_configurations/global/intent.json b/pype/configurations/defaults/system_configurations/global/intent.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/global/intent.json rename to pype/configurations/defaults/system_configurations/global/intent.json diff --git a/pype/configurations/defaults/studio_configurations/global/tools.json b/pype/configurations/defaults/system_configurations/global/tools.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/global/tools.json rename to pype/configurations/defaults/system_configurations/global/tools.json diff --git a/pype/configurations/defaults/studio_configurations/global/tray_modules.json b/pype/configurations/defaults/system_configurations/global/tray_modules.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/global/tray_modules.json rename to pype/configurations/defaults/system_configurations/global/tray_modules.json diff --git a/pype/configurations/defaults/studio_configurations/launchers/blender_2.80.toml b/pype/configurations/defaults/system_configurations/launchers/blender_2.80.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/blender_2.80.toml rename to pype/configurations/defaults/system_configurations/launchers/blender_2.80.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/blender_2.81.toml b/pype/configurations/defaults/system_configurations/launchers/blender_2.81.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/blender_2.81.toml rename to pype/configurations/defaults/system_configurations/launchers/blender_2.81.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/blender_2.82.toml b/pype/configurations/defaults/system_configurations/launchers/blender_2.82.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/blender_2.82.toml rename to pype/configurations/defaults/system_configurations/launchers/blender_2.82.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/blender_2.83.toml b/pype/configurations/defaults/system_configurations/launchers/blender_2.83.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/blender_2.83.toml rename to pype/configurations/defaults/system_configurations/launchers/blender_2.83.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/celaction_local.toml b/pype/configurations/defaults/system_configurations/launchers/celaction_local.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/celaction_local.toml rename to pype/configurations/defaults/system_configurations/launchers/celaction_local.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/celaction_publish.toml b/pype/configurations/defaults/system_configurations/launchers/celaction_publish.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/celaction_publish.toml rename to pype/configurations/defaults/system_configurations/launchers/celaction_publish.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/darwin/blender_2.82 b/pype/configurations/defaults/system_configurations/launchers/darwin/blender_2.82 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/darwin/blender_2.82 rename to pype/configurations/defaults/system_configurations/launchers/darwin/blender_2.82 diff --git a/pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17 b/pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17 rename to pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17 diff --git a/pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17_launch b/pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17_launch similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/darwin/harmony_17_launch rename to pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17_launch diff --git a/pype/configurations/defaults/studio_configurations/launchers/darwin/python3 b/pype/configurations/defaults/system_configurations/launchers/darwin/python3 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/darwin/python3 rename to pype/configurations/defaults/system_configurations/launchers/darwin/python3 diff --git a/pype/configurations/defaults/studio_configurations/launchers/harmony_17.toml b/pype/configurations/defaults/system_configurations/launchers/harmony_17.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/harmony_17.toml rename to pype/configurations/defaults/system_configurations/launchers/harmony_17.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/houdini_16.toml b/pype/configurations/defaults/system_configurations/launchers/houdini_16.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/houdini_16.toml rename to pype/configurations/defaults/system_configurations/launchers/houdini_16.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/houdini_17.toml b/pype/configurations/defaults/system_configurations/launchers/houdini_17.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/houdini_17.toml rename to pype/configurations/defaults/system_configurations/launchers/houdini_17.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/houdini_18.toml b/pype/configurations/defaults/system_configurations/launchers/houdini_18.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/houdini_18.toml rename to pype/configurations/defaults/system_configurations/launchers/houdini_18.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/maya2016 b/pype/configurations/defaults/system_configurations/launchers/linux/maya2016 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/maya2016 rename to pype/configurations/defaults/system_configurations/launchers/linux/maya2016 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/maya2017 b/pype/configurations/defaults/system_configurations/launchers/linux/maya2017 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/maya2017 rename to pype/configurations/defaults/system_configurations/launchers/linux/maya2017 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/maya2018 b/pype/configurations/defaults/system_configurations/launchers/linux/maya2018 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/maya2018 rename to pype/configurations/defaults/system_configurations/launchers/linux/maya2018 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/maya2019 b/pype/configurations/defaults/system_configurations/launchers/linux/maya2019 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/maya2019 rename to pype/configurations/defaults/system_configurations/launchers/linux/maya2019 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/maya2020 b/pype/configurations/defaults/system_configurations/launchers/linux/maya2020 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/maya2020 rename to pype/configurations/defaults/system_configurations/launchers/linux/maya2020 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/nuke11.3 b/pype/configurations/defaults/system_configurations/launchers/linux/nuke11.3 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/nuke11.3 rename to pype/configurations/defaults/system_configurations/launchers/linux/nuke11.3 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/nuke12.0 b/pype/configurations/defaults/system_configurations/launchers/linux/nuke12.0 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/nuke12.0 rename to pype/configurations/defaults/system_configurations/launchers/linux/nuke12.0 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio11.3 b/pype/configurations/defaults/system_configurations/launchers/linux/nukestudio11.3 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio11.3 rename to pype/configurations/defaults/system_configurations/launchers/linux/nukestudio11.3 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio12.0 b/pype/configurations/defaults/system_configurations/launchers/linux/nukestudio12.0 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/nukestudio12.0 rename to pype/configurations/defaults/system_configurations/launchers/linux/nukestudio12.0 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/nukex11.3 b/pype/configurations/defaults/system_configurations/launchers/linux/nukex11.3 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/nukex11.3 rename to pype/configurations/defaults/system_configurations/launchers/linux/nukex11.3 diff --git a/pype/configurations/defaults/studio_configurations/launchers/linux/nukex12.0 b/pype/configurations/defaults/system_configurations/launchers/linux/nukex12.0 similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/linux/nukex12.0 rename to pype/configurations/defaults/system_configurations/launchers/linux/nukex12.0 diff --git a/pype/configurations/defaults/studio_configurations/launchers/maya_2016.toml b/pype/configurations/defaults/system_configurations/launchers/maya_2016.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/maya_2016.toml rename to pype/configurations/defaults/system_configurations/launchers/maya_2016.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/maya_2017.toml b/pype/configurations/defaults/system_configurations/launchers/maya_2017.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/maya_2017.toml rename to pype/configurations/defaults/system_configurations/launchers/maya_2017.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/maya_2018.toml b/pype/configurations/defaults/system_configurations/launchers/maya_2018.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/maya_2018.toml rename to pype/configurations/defaults/system_configurations/launchers/maya_2018.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/maya_2019.toml b/pype/configurations/defaults/system_configurations/launchers/maya_2019.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/maya_2019.toml rename to pype/configurations/defaults/system_configurations/launchers/maya_2019.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/maya_2020.toml b/pype/configurations/defaults/system_configurations/launchers/maya_2020.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/maya_2020.toml rename to pype/configurations/defaults/system_configurations/launchers/maya_2020.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayabatch_2019.toml b/pype/configurations/defaults/system_configurations/launchers/mayabatch_2019.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayabatch_2019.toml rename to pype/configurations/defaults/system_configurations/launchers/mayabatch_2019.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayabatch_2020.toml b/pype/configurations/defaults/system_configurations/launchers/mayabatch_2020.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayabatch_2020.toml rename to pype/configurations/defaults/system_configurations/launchers/mayabatch_2020.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayapy2016.toml b/pype/configurations/defaults/system_configurations/launchers/mayapy2016.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayapy2016.toml rename to pype/configurations/defaults/system_configurations/launchers/mayapy2016.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayapy2017.toml b/pype/configurations/defaults/system_configurations/launchers/mayapy2017.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayapy2017.toml rename to pype/configurations/defaults/system_configurations/launchers/mayapy2017.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayapy2018.toml b/pype/configurations/defaults/system_configurations/launchers/mayapy2018.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayapy2018.toml rename to pype/configurations/defaults/system_configurations/launchers/mayapy2018.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayapy2019.toml b/pype/configurations/defaults/system_configurations/launchers/mayapy2019.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayapy2019.toml rename to pype/configurations/defaults/system_configurations/launchers/mayapy2019.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/mayapy2020.toml b/pype/configurations/defaults/system_configurations/launchers/mayapy2020.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/mayapy2020.toml rename to pype/configurations/defaults/system_configurations/launchers/mayapy2020.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/myapp.toml b/pype/configurations/defaults/system_configurations/launchers/myapp.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/myapp.toml rename to pype/configurations/defaults/system_configurations/launchers/myapp.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nuke_10.0.toml b/pype/configurations/defaults/system_configurations/launchers/nuke_10.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nuke_10.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nuke_10.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nuke_11.0.toml b/pype/configurations/defaults/system_configurations/launchers/nuke_11.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nuke_11.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nuke_11.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nuke_11.2.toml b/pype/configurations/defaults/system_configurations/launchers/nuke_11.2.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nuke_11.2.toml rename to pype/configurations/defaults/system_configurations/launchers/nuke_11.2.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nuke_11.3.toml b/pype/configurations/defaults/system_configurations/launchers/nuke_11.3.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nuke_11.3.toml rename to pype/configurations/defaults/system_configurations/launchers/nuke_11.3.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nuke_12.0.toml b/pype/configurations/defaults/system_configurations/launchers/nuke_12.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nuke_12.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nuke_12.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukestudio_10.0.toml b/pype/configurations/defaults/system_configurations/launchers/nukestudio_10.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukestudio_10.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nukestudio_10.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.0.toml b/pype/configurations/defaults/system_configurations/launchers/nukestudio_11.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nukestudio_11.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.2.toml b/pype/configurations/defaults/system_configurations/launchers/nukestudio_11.2.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.2.toml rename to pype/configurations/defaults/system_configurations/launchers/nukestudio_11.2.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.3.toml b/pype/configurations/defaults/system_configurations/launchers/nukestudio_11.3.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukestudio_11.3.toml rename to pype/configurations/defaults/system_configurations/launchers/nukestudio_11.3.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukestudio_12.0.toml b/pype/configurations/defaults/system_configurations/launchers/nukestudio_12.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukestudio_12.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nukestudio_12.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukex_10.0.toml b/pype/configurations/defaults/system_configurations/launchers/nukex_10.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukex_10.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nukex_10.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukex_11.0.toml b/pype/configurations/defaults/system_configurations/launchers/nukex_11.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukex_11.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nukex_11.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukex_11.2.toml b/pype/configurations/defaults/system_configurations/launchers/nukex_11.2.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukex_11.2.toml rename to pype/configurations/defaults/system_configurations/launchers/nukex_11.2.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukex_11.3.toml b/pype/configurations/defaults/system_configurations/launchers/nukex_11.3.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukex_11.3.toml rename to pype/configurations/defaults/system_configurations/launchers/nukex_11.3.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/nukex_12.0.toml b/pype/configurations/defaults/system_configurations/launchers/nukex_12.0.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/nukex_12.0.toml rename to pype/configurations/defaults/system_configurations/launchers/nukex_12.0.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/photoshop_2020.toml b/pype/configurations/defaults/system_configurations/launchers/photoshop_2020.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/photoshop_2020.toml rename to pype/configurations/defaults/system_configurations/launchers/photoshop_2020.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/premiere_2019.toml b/pype/configurations/defaults/system_configurations/launchers/premiere_2019.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/premiere_2019.toml rename to pype/configurations/defaults/system_configurations/launchers/premiere_2019.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/premiere_2020.toml b/pype/configurations/defaults/system_configurations/launchers/premiere_2020.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/premiere_2020.toml rename to pype/configurations/defaults/system_configurations/launchers/premiere_2020.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/python_2.toml b/pype/configurations/defaults/system_configurations/launchers/python_2.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/python_2.toml rename to pype/configurations/defaults/system_configurations/launchers/python_2.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/python_3.toml b/pype/configurations/defaults/system_configurations/launchers/python_3.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/python_3.toml rename to pype/configurations/defaults/system_configurations/launchers/python_3.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/resolve_16.toml b/pype/configurations/defaults/system_configurations/launchers/resolve_16.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/resolve_16.toml rename to pype/configurations/defaults/system_configurations/launchers/resolve_16.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/shell.toml b/pype/configurations/defaults/system_configurations/launchers/shell.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/shell.toml rename to pype/configurations/defaults/system_configurations/launchers/shell.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/storyboardpro_7.toml b/pype/configurations/defaults/system_configurations/launchers/storyboardpro_7.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/storyboardpro_7.toml rename to pype/configurations/defaults/system_configurations/launchers/storyboardpro_7.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/unreal_4.24.toml b/pype/configurations/defaults/system_configurations/launchers/unreal_4.24.toml similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/unreal_4.24.toml rename to pype/configurations/defaults/system_configurations/launchers/unreal_4.24.toml diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.80.bat b/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.80.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.80.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/blender_2.80.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.81.bat b/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.81.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.81.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/blender_2.81.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.82.bat b/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.82.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.82.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/blender_2.82.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.83.bat b/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.83.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/blender_2.83.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/blender_2.83.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/celaction_local.bat b/pype/configurations/defaults/system_configurations/launchers/windows/celaction_local.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/celaction_local.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/celaction_local.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/celaction_publish.bat b/pype/configurations/defaults/system_configurations/launchers/windows/celaction_publish.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/celaction_publish.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/celaction_publish.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/harmony_17.bat b/pype/configurations/defaults/system_configurations/launchers/windows/harmony_17.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/harmony_17.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/harmony_17.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/houdini_16.bat b/pype/configurations/defaults/system_configurations/launchers/windows/houdini_16.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/houdini_16.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/houdini_16.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/houdini_17.bat b/pype/configurations/defaults/system_configurations/launchers/windows/houdini_17.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/houdini_17.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/houdini_17.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/houdini_18.bat b/pype/configurations/defaults/system_configurations/launchers/windows/houdini_18.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/houdini_18.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/houdini_18.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/maya2016.bat b/pype/configurations/defaults/system_configurations/launchers/windows/maya2016.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/maya2016.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/maya2016.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/maya2017.bat b/pype/configurations/defaults/system_configurations/launchers/windows/maya2017.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/maya2017.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/maya2017.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/maya2018.bat b/pype/configurations/defaults/system_configurations/launchers/windows/maya2018.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/maya2018.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/maya2018.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/maya2019.bat b/pype/configurations/defaults/system_configurations/launchers/windows/maya2019.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/maya2019.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/maya2019.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/maya2020.bat b/pype/configurations/defaults/system_configurations/launchers/windows/maya2020.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/maya2020.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/maya2020.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2019.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2019.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2019.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2019.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2020.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2020.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayabatch2020.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2020.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2016.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2016.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2016.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayapy2016.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2017.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2017.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2017.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayapy2017.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2018.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2018.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2018.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayapy2018.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2019.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2019.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2019.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayapy2019.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2020.bat b/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2020.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/mayapy2020.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/mayapy2020.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nuke10.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nuke10.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nuke10.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nuke10.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nuke11.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nuke11.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.2.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nuke11.2.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.2.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nuke11.2.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.3.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nuke11.3.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nuke11.3.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nuke11.3.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nuke12.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nuke12.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nuke12.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nuke12.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio10.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio10.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio10.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukestudio10.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.2.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.2.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.2.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.2.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.3.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.3.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio11.3.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.3.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio12.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio12.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukestudio12.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukestudio12.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukex10.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukex10.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukex10.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukex10.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukex11.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukex11.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.2.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukex11.2.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.2.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukex11.2.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.3.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukex11.3.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukex11.3.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukex11.3.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/nukex12.0.bat b/pype/configurations/defaults/system_configurations/launchers/windows/nukex12.0.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/nukex12.0.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/nukex12.0.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/photoshop_2020.bat b/pype/configurations/defaults/system_configurations/launchers/windows/photoshop_2020.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/photoshop_2020.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/photoshop_2020.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2019.bat b/pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2019.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2020.bat b/pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/premiere_pro_2020.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/python3.bat b/pype/configurations/defaults/system_configurations/launchers/windows/python3.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/python3.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/python3.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/resolve_16.bat b/pype/configurations/defaults/system_configurations/launchers/windows/resolve_16.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/resolve_16.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/resolve_16.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/shell.bat b/pype/configurations/defaults/system_configurations/launchers/windows/shell.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/shell.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/shell.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/storyboardpro_7.bat b/pype/configurations/defaults/system_configurations/launchers/windows/storyboardpro_7.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/storyboardpro_7.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/storyboardpro_7.bat diff --git a/pype/configurations/defaults/studio_configurations/launchers/windows/unreal.bat b/pype/configurations/defaults/system_configurations/launchers/windows/unreal.bat similarity index 100% rename from pype/configurations/defaults/studio_configurations/launchers/windows/unreal.bat rename to pype/configurations/defaults/system_configurations/launchers/windows/unreal.bat diff --git a/pype/configurations/defaults/studio_configurations/muster/templates_mapping.json b/pype/configurations/defaults/system_configurations/muster/templates_mapping.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/muster/templates_mapping.json rename to pype/configurations/defaults/system_configurations/muster/templates_mapping.json diff --git a/pype/configurations/defaults/studio_configurations/standalone_publish/families.json b/pype/configurations/defaults/system_configurations/standalone_publish/families.json similarity index 100% rename from pype/configurations/defaults/studio_configurations/standalone_publish/families.json rename to pype/configurations/defaults/system_configurations/standalone_publish/families.json From 0cca3c68d10ca993cee378b3b9b52fc26eb80e77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:19:46 +0200 Subject: [PATCH 795/947] fixed last place where is used stuido instead of system --- pype/configurations/config.py | 4 ++-- pype/tools/config_setting/config_setting/widgets/base.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pype/configurations/config.py b/pype/configurations/config.py index 147570acd4..de310645ac 100644 --- a/pype/configurations/config.py +++ b/pype/configurations/config.py @@ -13,7 +13,7 @@ POP_KEY = "__pop_key__" # Paths to studio and project overrides STUDIO_OVERRIDES_PATH = os.environ["PYPE_CONFIG"] -SYSTEM_CONFIGURATIONS_DIR = "studio_configurations" +SYSTEM_CONFIGURATIONS_DIR = "system_configurations" SYSTEM_CONFIGURATIONS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, SYSTEM_CONFIGURATIONS_DIR ) @@ -147,7 +147,7 @@ def load_jsons_from_dir(path, *args, **kwargs): return output -def studio_configurations(*args, **kwargs): +def system_configurations(*args, **kwargs): return load_jsons_from_dir(SYSTEM_CONFIGURATIONS_PATH, *args, **kwargs) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 82a5d024b0..aaa9831c61 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -135,7 +135,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = all_values["system"] # Load studio data with metadata - current_configurations = config.studio_configurations() + current_configurations = config.system_configurations() keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: @@ -171,10 +171,9 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() def _update_values(self): - config.default_configuration() - values = {"system": config.studio_configurations()} + system_values = {"system": config.system_configurations()} for input_field in self.input_fields: - input_field.update_studio_values(values) + input_field.update_studio_values(system_values) for input_field in self.input_fields: input_field.hierarchical_style_update() From 35bc72014e5046cdeb9774b7995ab02bb4cd81ed Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:21:22 +0200 Subject: [PATCH 796/947] bases sets also default values --- .../config_setting/config_setting/widgets/base.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index aaa9831c61..55ca6096de 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -171,6 +171,11 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() def _update_values(self): + default_values = config.default_configuration() + default_values = {"system": default_values["system_configurations"]} + for input_field in self.input_fields: + input_field.update_default_values(default_values) + system_values = {"system": config.system_configurations()} for input_field in self.input_fields: input_field.update_studio_values(system_values) @@ -552,9 +557,14 @@ class ProjectWidget(QtWidgets.QWidget): self._update_values() def _update_values(self): - values = {"project": config.global_project_configurations()} + default_values = config.default_configuration() + default_values = {"project": default_values["project_configurations"]} for input_field in self.input_fields: - input_field.update_studio_values(values) + input_field.update_default_values(default_values) + + studio_values = {"project": config.global_project_configurations()} + for input_field in self.input_fields: + input_field.update_studio_values(studio_values) for input_field in self.input_fields: input_field.hierarchical_style_update() From 3504f06d7b14f26e6068279bf3e238e03f555d47 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:32:26 +0200 Subject: [PATCH 797/947] removed log from abstract attributes --- pype/tools/config_setting/config_setting/widgets/widgets.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index a76f4f6f35..9e814a81e5 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -234,12 +234,6 @@ class AbstractConfigObject: ) return super(AbstractConfigObject, self).__getattribute__(name) - @property - def log(self): - raise NotImplementedError( - "{} does not have implemented `log`".format(self) - ) - @property def is_modified(self): """Has object any changes that require saving.""" From 2eb16374dc69c3118738c515f18a52092c181186 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:43:54 +0200 Subject: [PATCH 798/947] modified configurations files to match more current schemas for able to test --- .../nukestudio/tags.json | 262 ------------------ .../{ => plugins}/celaction/publish.json | 0 .../{ => plugins}/config.json | 0 .../{ => plugins}/ftrack/publish.json | 0 .../{ => plugins}/global/create.json | 0 .../{ => plugins}/global/filter.json | 0 .../{ => plugins}/global/load.json | 0 .../{ => plugins}/global/publish.json | 4 +- .../{ => plugins}/maya/create.json | 0 .../{ => plugins}/maya/filter.json | 0 .../{ => plugins}/maya/load.json | 0 .../{ => plugins}/maya/publish.json | 0 .../{ => plugins}/maya/workfile_build.json | 0 .../{ => plugins}/nuke/create.json | 0 .../{ => plugins}/nuke/load.json | 0 .../{ => plugins}/nuke/publish.json | 0 .../{ => plugins}/nuke/workfile_build.json | 0 .../{ => plugins}/nukestudio/filter.json | 0 .../{ => plugins}/nukestudio/publish.json | 0 .../{ => plugins}/resolve/create.json | 0 .../standalonepublisher/publish.json | 0 .../{ => plugins}/test/create.json | 0 .../{ => plugins}/test/publish.json | 0 23 files changed, 3 insertions(+), 263 deletions(-) delete mode 100644 pype/configurations/defaults/project_configurations/nukestudio/tags.json rename pype/configurations/defaults/project_configurations/{ => plugins}/celaction/publish.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/config.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/ftrack/publish.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/global/create.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/global/filter.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/global/load.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/global/publish.json (98%) rename pype/configurations/defaults/project_configurations/{ => plugins}/maya/create.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/maya/filter.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/maya/load.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/maya/publish.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/maya/workfile_build.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/nuke/create.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/nuke/load.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/nuke/publish.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/nuke/workfile_build.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/nukestudio/filter.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/nukestudio/publish.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/resolve/create.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/standalonepublisher/publish.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/test/create.json (100%) rename pype/configurations/defaults/project_configurations/{ => plugins}/test/publish.json (100%) diff --git a/pype/configurations/defaults/project_configurations/nukestudio/tags.json b/pype/configurations/defaults/project_configurations/nukestudio/tags.json deleted file mode 100644 index 56fcfcbce9..0000000000 --- a/pype/configurations/defaults/project_configurations/nukestudio/tags.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "Hierarchy": { - "editable": "1", - "note": "{folder}/{sequence}/{shot}", - "icon": { - "path": "hierarchy.png" - }, - "metadata": { - "folder": "FOLDER_NAME", - "shot": "{clip}", - "track": "{track}", - "sequence": "{sequence}", - "episode": "EPISODE_NAME", - "root": "{projectroot}" - } - }, - "Source Resolution": { - "editable": "1", - "note": "Use source resolution", - "icon": { - "path": "resolution.png" - }, - "metadata": { - "family": "resolution" - } - }, - "Retiming": { - "editable": "1", - "note": "Clip has retime or TimeWarp effects (or multiple effects stacked on the clip)", - "icon": { - "path": "retiming.png" - }, - "metadata": { - "family": "retiming", - "marginIn": 1, - "marginOut": 1 - } - }, - "Frame start": { - "editable": "1", - "note": "Starting frame for comps. \n\n> Use `value` and add either number or write `source` (if you want to preserve source frame numbering)", - "icon": { - "path": "icons:TagBackground.png" - }, - "metadata": { - "family": "frameStart", - "value": "1001" - } - }, - "[Lenses]": { - "Set lense here": { - "editable": "1", - "note": "Adjust parameters of your lense and then drop to clip. Remember! You can always overwrite on clip", - "icon": { - "path": "lense.png" - }, - "metadata": { - "focalLengthMm": 57 - - } - } - }, - "[Subsets]": { - "Audio": { - "editable": "1", - "note": "Export with Audio", - "icon": { - "path": "volume.png" - }, - "metadata": { - "family": "audio", - "subset": "main" - } - }, - "plateFg": { - "editable": "1", - "note": "Add to publish to \"forground\" subset. Change metadata subset name if different order number", - "icon": { - "path": "z_layer_fg.png" - }, - "metadata": { - "family": "plate", - "subset": "Fg01" - } - }, - "plateBg": { - "editable": "1", - "note": "Add to publish to \"background\" subset. Change metadata subset name if different order number", - "icon": { - "path": "z_layer_bg.png" - }, - "metadata": { - "family": "plate", - "subset": "Bg01" - } - }, - "plateRef": { - "editable": "1", - "note": "Add to publish to \"reference\" subset.", - "icon": { - "path": "icons:Reference.png" - }, - "metadata": { - "family": "plate", - "subset": "Ref" - } - }, - "plateMain": { - "editable": "1", - "note": "Add to publish to \"main\" subset.", - "icon": { - "path": "z_layer_main.png" - }, - "metadata": { - "family": "plate", - "subset": "main" - } - }, - "plateProxy": { - "editable": "1", - "note": "Add to publish to \"proxy\" subset.", - "icon": { - "path": "z_layer_main.png" - }, - "metadata": { - "family": "plate", - "subset": "proxy" - } - }, - "review": { - "editable": "1", - "note": "Upload to Ftrack as review component.", - "icon": { - "path": "review.png" - }, - "metadata": { - "family": "review", - "track": "review" - } - } - }, - "[Handles]": { - "start: add 20 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "20", - "args": "{'op':'add','where':'start'}" - } - }, - "start: add 10 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "10", - "args": "{'op':'add','where':'start'}" - } - }, - "start: add 5 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "5", - "args": "{'op':'add','where':'start'}" - } - }, - "start: add 0 frames": { - "editable": "1", - "note": "Adding frames to start of selected clip", - "icon": { - "path": "3_add_handles_start.png" - }, - "metadata": { - "family": "handles", - "value": "0", - "args": "{'op':'add','where':'start'}" - } - }, - "end: add 20 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "20", - "args": "{'op':'add','where':'end'}" - } - }, - "end: add 10 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "10", - "args": "{'op':'add','where':'end'}" - } - }, - "end: add 5 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "5", - "args": "{'op':'add','where':'end'}" - } - }, - "end: add 0 frames": { - "editable": "1", - "note": "Adding frames to end of selected clip", - "icon": { - "path": "1_add_handles_end.png" - }, - "metadata": { - "family": "handles", - "value": "0", - "args": "{'op':'add','where':'end'}" - } - } - }, - "NukeScript": { - "editable": "1", - "note": "Collecting track items to Nuke scripts.", - "icon": { - "path": "icons:TagNuke.png" - }, - "metadata": { - "family": "nukescript", - "subset": "main" - } - }, - "Comment": { - "editable": "1", - "note": "Comment on a shot.", - "icon": { - "path": "icons:TagComment.png" - }, - "metadata": { - "family": "comment", - "subset": "main" - } - } -} diff --git a/pype/configurations/defaults/project_configurations/celaction/publish.json b/pype/configurations/defaults/project_configurations/plugins/celaction/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/celaction/publish.json rename to pype/configurations/defaults/project_configurations/plugins/celaction/publish.json diff --git a/pype/configurations/defaults/project_configurations/config.json b/pype/configurations/defaults/project_configurations/plugins/config.json similarity index 100% rename from pype/configurations/defaults/project_configurations/config.json rename to pype/configurations/defaults/project_configurations/plugins/config.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/publish.json b/pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/publish.json rename to pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json diff --git a/pype/configurations/defaults/project_configurations/global/create.json b/pype/configurations/defaults/project_configurations/plugins/global/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/create.json rename to pype/configurations/defaults/project_configurations/plugins/global/create.json diff --git a/pype/configurations/defaults/project_configurations/global/filter.json b/pype/configurations/defaults/project_configurations/plugins/global/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/filter.json rename to pype/configurations/defaults/project_configurations/plugins/global/filter.json diff --git a/pype/configurations/defaults/project_configurations/global/load.json b/pype/configurations/defaults/project_configurations/plugins/global/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/load.json rename to pype/configurations/defaults/project_configurations/plugins/global/load.json diff --git a/pype/configurations/defaults/project_configurations/global/publish.json b/pype/configurations/defaults/project_configurations/plugins/global/publish.json similarity index 98% rename from pype/configurations/defaults/project_configurations/global/publish.json rename to pype/configurations/defaults/project_configurations/plugins/global/publish.json index 3c5de85e68..d531f1aa47 100644 --- a/pype/configurations/defaults/project_configurations/global/publish.json +++ b/pype/configurations/defaults/project_configurations/plugins/global/publish.json @@ -3,6 +3,7 @@ "enabled": false }, "ExtractJpegEXR": { + "enabled": true, "ffmpeg_args": { "input": [ "-gamma 2.2" @@ -74,6 +75,7 @@ ] }, "IntegrateAssetNew": { + "enabled": true, "template_name_profiles": { "publish": { "families": [], @@ -94,4 +96,4 @@ "deadline_pool": "", "deadline_group": "" } -} \ No newline at end of file +} diff --git a/pype/configurations/defaults/project_configurations/maya/create.json b/pype/configurations/defaults/project_configurations/plugins/maya/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/maya/create.json rename to pype/configurations/defaults/project_configurations/plugins/maya/create.json diff --git a/pype/configurations/defaults/project_configurations/maya/filter.json b/pype/configurations/defaults/project_configurations/plugins/maya/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/maya/filter.json rename to pype/configurations/defaults/project_configurations/plugins/maya/filter.json diff --git a/pype/configurations/defaults/project_configurations/maya/load.json b/pype/configurations/defaults/project_configurations/plugins/maya/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/maya/load.json rename to pype/configurations/defaults/project_configurations/plugins/maya/load.json diff --git a/pype/configurations/defaults/project_configurations/maya/publish.json b/pype/configurations/defaults/project_configurations/plugins/maya/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/maya/publish.json rename to pype/configurations/defaults/project_configurations/plugins/maya/publish.json diff --git a/pype/configurations/defaults/project_configurations/maya/workfile_build.json b/pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json similarity index 100% rename from pype/configurations/defaults/project_configurations/maya/workfile_build.json rename to pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json diff --git a/pype/configurations/defaults/project_configurations/nuke/create.json b/pype/configurations/defaults/project_configurations/plugins/nuke/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/nuke/create.json rename to pype/configurations/defaults/project_configurations/plugins/nuke/create.json diff --git a/pype/configurations/defaults/project_configurations/nuke/load.json b/pype/configurations/defaults/project_configurations/plugins/nuke/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/nuke/load.json rename to pype/configurations/defaults/project_configurations/plugins/nuke/load.json diff --git a/pype/configurations/defaults/project_configurations/nuke/publish.json b/pype/configurations/defaults/project_configurations/plugins/nuke/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/nuke/publish.json rename to pype/configurations/defaults/project_configurations/plugins/nuke/publish.json diff --git a/pype/configurations/defaults/project_configurations/nuke/workfile_build.json b/pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json similarity index 100% rename from pype/configurations/defaults/project_configurations/nuke/workfile_build.json rename to pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json diff --git a/pype/configurations/defaults/project_configurations/nukestudio/filter.json b/pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/nukestudio/filter.json rename to pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json diff --git a/pype/configurations/defaults/project_configurations/nukestudio/publish.json b/pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/nukestudio/publish.json rename to pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json diff --git a/pype/configurations/defaults/project_configurations/resolve/create.json b/pype/configurations/defaults/project_configurations/plugins/resolve/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/resolve/create.json rename to pype/configurations/defaults/project_configurations/plugins/resolve/create.json diff --git a/pype/configurations/defaults/project_configurations/standalonepublisher/publish.json b/pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/standalonepublisher/publish.json rename to pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json diff --git a/pype/configurations/defaults/project_configurations/test/create.json b/pype/configurations/defaults/project_configurations/plugins/test/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/test/create.json rename to pype/configurations/defaults/project_configurations/plugins/test/create.json diff --git a/pype/configurations/defaults/project_configurations/test/publish.json b/pype/configurations/defaults/project_configurations/plugins/test/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/test/publish.json rename to pype/configurations/defaults/project_configurations/plugins/test/publish.json From 9f9bad6c5425c97b944be59926db94450705571a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 12:44:16 +0200 Subject: [PATCH 799/947] first commit of update_default_values --- .../config_setting/widgets/anatomy_inputs.py | 35 +++++++++++++++ .../config_setting/widgets/inputs.py | 44 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index fbc3a3a2ed..43146f7442 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -75,6 +75,10 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.value_changed.connect(self._on_value_change) + def update_default_values(self, value): + self.root_widget.update_default_values(value) + self.templates_widget.update_default_values(value) + def update_studio_values(self, parent_values): self._state = None self._child_state = None @@ -255,6 +259,33 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def is_multiroot(self): return self.multiroot_checkbox.isChecked() + def update_default_values(self, parent_values): + self._state = None + self._multiroot_state = None + + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + + self.global_is_multiroot = is_multiroot + self.was_multiroot = is_multiroot + self.set_multiroot(is_multiroot) + + if is_multiroot: + self.singleroot_widget.update_studio_values(NOT_SET) + self.multiroot_widget.update_studio_values(value) + else: + self.singleroot_widget.update_studio_values(value) + self.multiroot_widget.update_studio_values(NOT_SET) + def update_studio_values(self, parent_values): self._state = None self._multiroot_state = None @@ -486,6 +517,10 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): layout.addWidget(body_widget) + def update_default_values(self, values): + self._state = None + self.value_input.update_default_values(values) + def update_studio_values(self, values): self._state = None self.value_input.update_studio_values(values) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 057ed3b584..27de95c402 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -174,6 +174,26 @@ class ConfigObject(AbstractConfigObject): class InputObject(ConfigObject): + def update_default_values(self, parent_values): + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if value is NOT_SET: + if self._as_widget: + print(self) + elif hasattr(self, "_parent"): + print(self._parent.key, self.key, self) + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + self.default_value = value + if not self.has_studio_override: + print(value) + self.set_value(value) + def overrides(self): if not self.is_overriden: return NOT_SET, False @@ -1666,6 +1686,16 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() + def update_default_values(self, parent_values): + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + for item in self.input_fields: + item.update_default_values(value) + def update_studio_values(self, parent_values): value = NOT_SET if parent_values is not NOT_SET: @@ -1932,6 +1962,16 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() + def update_default_values(self, parent_values): + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + for item in self.input_fields: + item.update_default_values(value) + def update_studio_values(self, parent_values): value = NOT_SET if parent_values is not NOT_SET: @@ -2367,6 +2407,10 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): for item in self.input_fields: item.set_as_overriden() + def update_default_values(self, value): + for item in self.input_fields: + item.update_default_values(value) + def update_studio_values(self, value): for item in self.input_fields: item.update_studio_values(value) From 24753d6af787e511768c1d759ae8693fa9485162 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 13:34:27 +0200 Subject: [PATCH 800/947] cleaned debug part --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 27de95c402..989980f2a0 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -182,16 +182,12 @@ class InputObject(ConfigObject): value = parent_values.get(self.key, NOT_SET) if value is NOT_SET: - if self._as_widget: - print(self) - elif hasattr(self, "_parent"): - print(self._parent.key, self.key, self) raise ValueError( "Default value is not set. This is implementation BUG." ) + self.default_value = value if not self.has_studio_override: - print(value) self.set_value(value) def overrides(self): From dbfe43e821372445a0bceb48d8890e534d7c9d7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 14:03:17 +0200 Subject: [PATCH 801/947] update studio_values should work with new default_values --- .../config_setting/widgets/inputs.py | 216 ++++-------------- 1 file changed, 42 insertions(+), 174 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 989980f2a0..fa07419fde 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -190,6 +190,24 @@ class InputObject(ConfigObject): if not self.has_studio_override: self.set_value(value) + def update_studio_values(self, parent_values): + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self.studio_value = value + if value is not NOT_SET: + self.set_value(value) + self._has_studio_override = True + + else: + self.set_value(self.default_value) + self._has_studio_override = False + + self._is_modified = False + def overrides(self): if not self.is_overriden: return NOT_SET, False @@ -199,7 +217,10 @@ class InputObject(ConfigObject): self.update_style() def remove_overrides(self): - self.set_value(self.start_value) + if self.has_studio_override: + self.set_value(self.studio_value) + else: + self.set_value(self.default_value) self._is_overriden = False self._is_modified = False @@ -217,7 +238,10 @@ class InputObject(ConfigObject): if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False - value = self.start_value + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value else: self._is_overriden = True self._was_overriden = True @@ -234,7 +258,10 @@ class InputObject(ConfigObject): ): self.set_value(self.override_value) else: - self.set_value(self.start_value) + if self.has_studio_override: + self.set_value(self.studio_value) + else: + self.set_value(self.defaul_value) if not self.is_overidable: self._is_modified = self.studio_value != self.item_value() @@ -290,7 +317,6 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -316,24 +342,6 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.checkbox.stateChanged.connect(self._on_value_change) - def update_studio_values(self, parent_values): - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.set_value(value) - - elif self.default_value is not NOT_SET: - self.set_value(self.default_value) - - self.studio_value = value - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value - def set_value(self, value): # Ignore value change because if `self.isChecked()` has same # value as `value` the `_on_value_change` is not triggered @@ -399,7 +407,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -426,25 +433,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.input_field.valueChanged.connect(self._on_value_change) - def update_studio_values(self, parent_values): - value = NOT_SET - if self._as_widget: - value = parent_values - else: - if parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.set_value(value) - - elif self.default_value is not NOT_SET: - self.set_value(self.default_value) - - self.studio_value = value - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value - def set_value(self, value): self.input_field.setValue(value) @@ -510,7 +498,6 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -535,24 +522,6 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.text_input.textChanged.connect(self._on_value_change) - def update_studio_values(self, parent_values): - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.set_value(value) - - elif self.default_value is not NOT_SET: - self.set_value(self.default_value) - - self.studio_value = value - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value - def set_value(self, value): if self.multiline: self.text_input.setPlainText(value) @@ -622,7 +591,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -642,24 +610,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self.path_input.textChanged.connect(self._on_value_change) - def update_studio_values(self, parent_values): - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.set_value(value) - - elif self.default_value is not NOT_SET: - self.set_value(self.default_value) - - self.studio_value = value - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value - def set_value(self, value): self.path_input.setText(value) @@ -785,7 +735,6 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -811,25 +760,10 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.text_input.textChanged.connect(self._on_value_change) def update_studio_values(self, parent_values): - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if value is not NOT_SET: - self.set_value(value) - - elif self.default_value is not NOT_SET: - self.set_value(self.default_value) + super(RawJsonWidget, self).update_studio_values(parent_values) self._is_invalid = self.text_input.has_invalid_value() - self.studio_value = value - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value - def set_value(self, value): self.text_input.set_value(value) @@ -982,7 +916,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET self.key = input_data["key"] @@ -1015,33 +948,8 @@ class ListWidget(QtWidgets.QWidget, InputObject): return len(self.input_fields) def update_studio_values(self, parent_values): - old_inputs = tuple(self.input_fields) + super(ListWidget, self).update_studio_values(parent_values) - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - self.studio_value = value - - if value is not NOT_SET: - for item_value in value: - self.add_row(value=item_value) - - elif self.default_value is not NOT_SET: - for item_value in self.default_value: - self.add_row(value=item_value) - - for old_input in old_inputs: - self.remove_row(old_input) - - if self.count() == 0: - self.add_row(is_empty=True) - - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value self.hierarchical_style_update() def set_value(self, value): @@ -1052,6 +960,9 @@ class ListWidget(QtWidgets.QWidget, InputObject): for input_field in previous_inputs: self.remove_row(input_field) + if self.count() == 0: + self.add_row(is_empty=True) + def _on_value_change(self, item=None): if self.ignore_value_changes: return @@ -1128,7 +1039,10 @@ class ListWidget(QtWidgets.QWidget, InputObject): if override_value is NOT_SET: self._is_overriden = False self._was_overriden = False - value = self.start_value + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value else: self._is_overriden = True self._was_overriden = True @@ -1326,7 +1240,6 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET any_parent_is_group = parent.is_group if not any_parent_is_group: @@ -1380,35 +1293,6 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def count(self): return len(self.input_fields) - def update_studio_values(self, parent_values): - old_inputs = tuple(self.input_fields) - - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - self.studio_value = value - - if value is not NOT_SET: - for item_key, item_value in value.items(): - self.add_row(key=item_key, value=item_value) - - elif self.default_value is not NOT_SET: - for item_key, item_value in self.default_value.items(): - self.add_row(key=item_key, value=item_value) - - for old_input in old_inputs: - self.remove_row(old_input) - - if self.count() == 0: - self.add_row(is_empty=True) - - self.start_value = self.item_value() - - self._is_modified = self.studio_value != self.start_value - def set_value(self, value): previous_inputs = tuple(self.input_fields) for item_key, item_value in value.items(): @@ -1417,6 +1301,9 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): for input_field in previous_inputs: self.remove_row(input_field) + if self.count() == 0: + self.add_row(is_empty=True) + def _on_value_change(self, item=None): fields_by_keys = collections.defaultdict(list) for input_field in self.input_fields: @@ -2066,7 +1953,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.default_value = NOT_SET self.studio_value = NOT_SET self.override_value = NOT_SET - self.start_value = NOT_SET self.input_fields = [] @@ -2143,24 +2029,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.setFocusProxy(self.input_fields[0]) self.content_layout.addWidget(proxy_widget) - def update_studio_values(self, parent_values): - value = NOT_SET - if self._as_widget: - value = parent_values - elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) - - if not self.multiplatform: - self.input_fields[0].update_studio_values(parent_values) - - elif self.multiplatform: - for input_field in self.input_fields: - input_field.update_studio_values(value) - - self.studio_value = value - self.start_value = self.item_value() - self._is_modified = self.studio_value != self.start_value - def apply_overrides(self, parent_values): self._is_modified = False self._state = None From 46944e87c996d2ceb987f6eafbaf9b2dda705405 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 14:25:14 +0200 Subject: [PATCH 802/947] simplified state stylesheets --- .../config_setting/style/style.css | 68 +++++-------------- 1 file changed, 17 insertions(+), 51 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index fe3bba366a..fef1278de1 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -53,21 +53,11 @@ QLabel[state="overriden"]:hover {color: #ffa64d;} QLabel[state="invalid"] {color: #ad2e2e; font-weight: bold;} QLabel[state="invalid"]:hover {color: #ad2e2e; font-weight: bold;} -QWidget[input-state="modified"] { - border-color: #137cbd; -} -QWidget[input-state="overriden-modified"] { - border-color: #137cbd; -} - -QWidget[input-state="overriden"] { - border-color: #ff8c1a; -} - -QWidget[input-state="invalid"] { - border-color: #ad2e2e; -} +QWidget[input-state="modified"] {border-color: #137cbd;} +QWidget[input-state="overriden-modified"] {border-color: #137cbd;} +QWidget[input-state="overriden"] {border-color: #ff8c1a;} +QWidget[input-state="invalid"] {border-color: #ad2e2e;} QPushButton { border: 1px solid #aaaaaa; @@ -105,19 +95,10 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } -#DictKey[state="modified"] { - border-color: #137cbd; -} - -#DictKey[state="overriden"] { - border-color: #00f; -} -#DictKey[state="overriden-modified"] { - border-color: #0f0; -} -#DictKey[state="invalid"] { - border-color: #ad2e2e; -} +#DictKey[state="modified"] {border-color: #137cbd;} +#DictKey[state="overriden"] {border-color: #00f;} +#DictKey[state="overriden-modified"] {border-color: #0f0;} +#DictKey[state="invalid"] {border-color: #ad2e2e;} #DictLabel { font-weight: bold; @@ -144,33 +125,18 @@ QPushButton[btn-type="expand-toggle"] { border-color: #62839d; } -#SideLineWidget[state="child-modified"]{ - border-color: #106aa2; -} -#SideLineWidget[state="child-modified"]:hover{ - border-color: #137cbd; -} -#SideLineWidget[state="child-invalid"]{ - border-color: #ad2e2e; -} -#SideLineWidget[state="child-invalid"]:hover{ - border-color: #c93636; -} +#SideLineWidget[state="child-modified"] {border-color: #106aa2;} +#SideLineWidget[state="child-modified"]:hover {border-color: #137cbd;} -#SideLineWidget[state="child-overriden"]{ - border-color: #e67300; -} -#SideLineWidget[state="child-overriden"]:hover { - border-color: #ff8c1a; -} +#SideLineWidget[state="child-invalid"] {border-color: #ad2e2e;} +#SideLineWidget[state="child-invalid"]:hover {border-color: #c93636;} -#SideLineWidget[state="child-overriden-modified"] { - border-color: #106aa2; -} -#SideLineWidget[state="child-overriden-modified"]:hover { - border-color: #137cbd; -} +#SideLineWidget[state="child-overriden"] {border-color: #e67300;} +#SideLineWidget[state="child-overriden"]:hover {border-color: #ff8c1a;} + +#SideLineWidget[state="child-overriden-modified"] {border-color: #106aa2;} +#SideLineWidget[state="child-overriden-modified"]:hover {border-color: #137cbd;} QScrollBar:horizontal { height: 15px; From 5830b637d7b17c0bb0b18e5f9b59db9376bf50a8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 14:25:29 +0200 Subject: [PATCH 803/947] normla label is little bit darker --- pype/tools/config_setting/config_setting/style/style.css | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index fef1278de1..61581b9124 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -38,11 +38,15 @@ QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QPlainTextEdit:d QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus { border: 1px solid #ffffff; } -QLabel, QToolButton { +QToolButton { background: transparent; } -QLabel:hover {color: #ffffff;} +QLabel { + background: transparent; + color: #808080; +} +QLabel:hover {color: #999999;} QLabel[state="modified"] {color: #137cbd;} QLabel[state="modified"]:hover {color: #1798e8;} From ac116aaa02637ddf0efad6b27229eed8ad9288f2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 14:25:45 +0200 Subject: [PATCH 804/947] added child_has_studio_override to abstract methods --- .../config_setting/config_setting/widgets/widgets.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 9e814a81e5..5547f66597 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -302,6 +302,15 @@ class AbstractConfigObject: "{} does not have implemented setter method `ignore_value_changes`" ).format(self)) + @property + def child_has_studio_override(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_has_studio_override`".format( + self + ) + ) + @property def child_modified(self): """Any children item is modified.""" From 82a29d9c2734e1a23cebe4efcabcbb9fc6a9b945 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 14:25:58 +0200 Subject: [PATCH 805/947] implemented child_has_studio_override for inputs --- .../config_setting/widgets/inputs.py | 108 +++++++++++++++--- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index fa07419fde..a9cbc502e9 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -94,9 +94,13 @@ class ConfigObject(AbstractConfigObject): return {self.key: self.item_value()} @classmethod - def style_state(cls, is_invalid, is_overriden, is_modified): + def style_state( + cls, has_studio_override, is_invalid, is_overriden, is_modified + ): items = [] - if is_invalid: + if has_studio_override: + items.append("studio") + elif is_invalid: items.append("invalid") else: if is_overriden: @@ -261,10 +265,13 @@ class InputObject(ConfigObject): if self.has_studio_override: self.set_value(self.studio_value) else: - self.set_value(self.defaul_value) + self.set_value(self.default_value) if not self.is_overidable: - self._is_modified = self.studio_value != self.item_value() + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() self._is_overriden = False return @@ -274,6 +281,10 @@ class InputObject(ConfigObject): def set_as_overriden(self): self._is_overriden = True + @property + def child_has_studio_override(self): + return self.has_studio_override + @property def child_modified(self): return self.is_modified @@ -367,7 +378,10 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -456,7 +470,10 @@ class NumberWidget(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -548,7 +565,10 @@ class TextWidget(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -637,7 +657,10 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -788,7 +811,10 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -876,6 +902,10 @@ class ListItem(QtWidgets.QWidget, ConfigObject): return self.value_input.item_value() return NOT_SET + @property + def child_has_studio_override(self): + return self.value_input.child_has_studio_override + @property def child_modified(self): return self.value_input.child_modified @@ -1060,7 +1090,10 @@ class ListWidget(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -1345,7 +1378,10 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def update_style(self): state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -1633,10 +1669,14 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self.update_style() def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override child_modified = self.child_modified child_invalid = self.child_invalid child_state = self.style_state( - child_invalid, self.child_overriden, child_modified + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified ) if child_state: child_state = "child-{}".format(child_state) @@ -1649,7 +1689,10 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self._child_state = child_state state = self.style_state( - child_invalid, self.is_overriden, self.is_modified + child_has_studio_override, + child_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -1665,6 +1708,13 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): return self._is_modified or self.child_modified return False + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if input_field.child_has_studio_override: + return True + return False + @property def child_modified(self): for input_field in self.input_fields: @@ -1768,6 +1818,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): def update_style(self, *args, **kwargs): return + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if input_field.child_has_studio_override: + return True + return False + @property def child_modified(self): for input_field in self.input_fields: @@ -2085,10 +2142,14 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.value_changed.emit(self) def update_style(self, is_overriden=None): + child_has_studio_override = self.has_studio_override child_modified = self.child_modified child_invalid = self.child_invalid child_state = self.style_state( - child_invalid, self.child_overriden, child_modified + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified ) if child_state: child_state = "child-{}".format(child_state) @@ -2100,7 +2161,10 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): if not self._as_widget: state = self.style_state( - child_invalid, self.is_overriden, self.is_modified + child_has_studio_override, + child_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -2128,6 +2192,13 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): def set_as_overriden(self): self._is_overriden = True + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if input_field.child_has_studio_override: + return True + return False + @property def child_modified(self): for input_field in self.input_fields: @@ -2286,6 +2357,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): if self.any_parent_is_group: self.hierarchical_style_update() + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if input_field.child_has_studio_override: + return True + return False + @property def child_modified(self): for input_field in self.input_fields: From 84108c5fda60808b0b2ea4f8424eccfa1b16aa6a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 14:46:44 +0200 Subject: [PATCH 806/947] fixed state values and added styles for studio overrides --- .../config_setting/config_setting/style/style.css | 10 ++++++++-- .../config_setting/config_setting/widgets/inputs.py | 8 +++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 61581b9124..73c1854bee 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -48,6 +48,8 @@ QLabel { } QLabel:hover {color: #999999;} +QLabel[state="studio"] {color: #bfccd6;} +QLabel[state="studio"]:hover {color: #ffffff;} QLabel[state="modified"] {color: #137cbd;} QLabel[state="modified"]:hover {color: #1798e8;} QLabel[state="overriden-modified"] {color: #137cbd;} @@ -58,6 +60,7 @@ QLabel[state="invalid"] {color: #ad2e2e; font-weight: bold;} QLabel[state="invalid"]:hover {color: #ad2e2e; font-weight: bold;} +QWidget[input-state="studio"] {border-color: #bfccd6;} QWidget[input-state="modified"] {border-color: #137cbd;} QWidget[input-state="overriden-modified"] {border-color: #137cbd;} QWidget[input-state="overriden"] {border-color: #ff8c1a;} @@ -99,6 +102,7 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } +#DictKey[state="studio"] {border-color: #bfccd6;} #DictKey[state="modified"] {border-color: #137cbd;} #DictKey[state="overriden"] {border-color: #00f;} #DictKey[state="overriden-modified"] {border-color: #0f0;} @@ -118,7 +122,7 @@ QPushButton[btn-type="expand-toggle"] { #SideLineWidget { background-color: #31424e; border-style: solid; - border-color: #455c6e; + border-color: #808080; border-left-width: 3px; border-bottom-width: 0px; border-right-width: 0px; @@ -126,9 +130,11 @@ QPushButton[btn-type="expand-toggle"] { } #SideLineWidget:hover { - border-color: #62839d; + border-color: #999999; } +#SideLineWidget[state="child-studio"] {border-color: #455c6e;} +#SideLineWidget[state="child-studio"]:hover {border-color: #62839d;} #SideLineWidget[state="child-modified"] {border-color: #106aa2;} #SideLineWidget[state="child-modified"]:hover {border-color: #137cbd;} diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index a9cbc502e9..b0aaa5ed3d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -98,15 +98,17 @@ class ConfigObject(AbstractConfigObject): cls, has_studio_override, is_invalid, is_overriden, is_modified ): items = [] - if has_studio_override: - items.append("studio") - elif is_invalid: + if is_invalid: items.append("invalid") else: if is_overriden: items.append("overriden") if is_modified: items.append("modified") + + if not items and has_studio_override: + items.append("studio") + return "-".join(items) or cls.default_state def _discard_changes(self): From 3af3e46f1018307697ac8f49b7c49b33e7f94109 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 15:35:38 +0200 Subject: [PATCH 807/947] config split into lib and config file --- pype/configurations/config.py | 222 +++------------------------------- pype/configurations/lib.py | 208 +++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 206 deletions(-) create mode 100644 pype/configurations/lib.py diff --git a/pype/configurations/config.py b/pype/configurations/config.py index de310645ac..e19a27f33c 100644 --- a/pype/configurations/config.py +++ b/pype/configurations/config.py @@ -1,214 +1,24 @@ -import os -import json -import logging -import copy - -log = logging.getLogger(__name__) - -# Metadata keys for work with studio and project overrides -OVERRIDEN_KEY = "__overriden_keys__" -# NOTE key popping not implemented yet -POP_KEY = "__pop_key__" - -# Paths to studio and project overrides -STUDIO_OVERRIDES_PATH = os.environ["PYPE_CONFIG"] - -SYSTEM_CONFIGURATIONS_DIR = "system_configurations" -SYSTEM_CONFIGURATIONS_PATH = os.path.join( - STUDIO_OVERRIDES_PATH, SYSTEM_CONFIGURATIONS_DIR -) -PROJECT_CONFIGURATIONS_DIR = "project_configurations" -PROJECT_CONFIGURATIONS_PATH = os.path.join( - STUDIO_OVERRIDES_PATH, PROJECT_CONFIGURATIONS_DIR +from .lib import ( + apply_overrides, + default_configuration, + studio_system_configurations, + studio_project_configurations, + project_configurations_overrides ) -# Variable where cache of default configurations are stored -_DEFAULT_CONFIGURATIONS = None -# TODO remove this as is maybe deprecated -first_run = False +def system_configurations(): + default_values = default_configuration()["system_configurations"] + studio_values = studio_system_configurations() + return apply_overrides(default_values, studio_values) -def default_configuration(): - global _DEFAULT_CONFIGURATIONS - if _DEFAULT_CONFIGURATIONS is None: - current_dir = os.path.dirname(__file__) - defaults_path = os.path.join(current_dir, "defaults") - _DEFAULT_CONFIGURATIONS = load_jsons_from_dir(defaults_path) - return _DEFAULT_CONFIGURATIONS +def project_configurations(project_name): + default_values = default_configuration() + studio_values = studio_project_configurations() + studio_overrides = apply_overrides(default_values, studio_values) -def load_json(fpath): - # Load json data - with open(fpath, "r") as opened_file: - lines = opened_file.read().splitlines() + project_overrides = project_configurations_overrides(project_name) - # prepare json string - standard_json = "" - for line in lines: - # Remove all whitespace on both sides - line = line.strip() - - # Skip blank lines - if len(line) == 0: - continue - - standard_json += line - - # Check if has extra commas - extra_comma = False - if ",]" in standard_json or ",}" in standard_json: - extra_comma = True - standard_json = standard_json.replace(",]", "]") - standard_json = standard_json.replace(",}", "}") - - global first_run - if extra_comma and first_run: - log.error("Extra comma in json file: \"{}\"".format(fpath)) - - # return empty dict if file is empty - if standard_json == "": - if first_run: - log.error("Empty json file: \"{}\"".format(fpath)) - return {} - - # Try to parse string - try: - return json.loads(standard_json) - - except json.decoder.JSONDecodeError: - # Return empty dict if it is first time that decode error happened - if not first_run: - return {} - - # Repreduce the exact same exception but traceback contains better - # information about position of error in the loaded json - try: - with open(fpath, "r") as opened_file: - json.load(opened_file) - - except json.decoder.JSONDecodeError: - log.warning( - "File has invalid json format \"{}\"".format(fpath), - exc_info=True - ) - - return {} - - -def subkey_merge(_dict, value, keys): - key = keys.pop(0) - if not keys: - _dict[key] = value - return _dict - - if key not in _dict: - _dict[key] = {} - _dict[key] = subkey_merge(_dict[key], value, keys) - - return _dict - - -def load_jsons_from_dir(path, *args, **kwargs): - output = {} - - path = os.path.normpath(path) - if not os.path.exists(path): - # TODO warning - return output - - sub_keys = list(kwargs.pop("subkeys", args)) - for sub_key in tuple(sub_keys): - _path = os.path.join(path, sub_key) - if not os.path.exists(_path): - break - - path = _path - sub_keys.pop(0) - - base_len = len(path) + 1 - for base, _directories, filenames in os.walk(path): - base_items_str = base[base_len:] - if not base_items_str: - base_items = [] - else: - base_items = base_items_str.split(os.path.sep) - - for filename in filenames: - basename, ext = os.path.splitext(filename) - if ext == ".json": - full_path = os.path.join(base, filename) - value = load_json(full_path) - dict_keys = base_items + [basename] - output = subkey_merge(output, value, dict_keys) - - for sub_key in sub_keys: - output = output[sub_key] - return output - - -def system_configurations(*args, **kwargs): - return load_jsons_from_dir(SYSTEM_CONFIGURATIONS_PATH, *args, **kwargs) - - -def global_project_configurations(**kwargs): - return load_jsons_from_dir(PROJECT_CONFIGURATIONS_PATH, **kwargs) - - -def path_to_project_overrides(project_name): - project_configs_path = os.environ["PYPE_PROJECT_CONFIGS"] - dirpath = os.path.join(project_configs_path, project_name) - return os.path.join(dirpath, PROJECT_CONFIGURATIONS_DIR + ".json") - - -def project_configurations_overrides(project_name, **kwargs): - if not project_name: - return {} - - path_to_json = path_to_project_overrides(project_name) - if not os.path.exists(path_to_json): - return {} - return load_json(path_to_json) - - -def merge_overrides(global_dict, override_dict): - if OVERRIDEN_KEY in override_dict: - overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) - else: - overriden_keys = set() - - for key, value in override_dict.items(): - if value == POP_KEY: - global_dict.pop(key) - - elif ( - key in overriden_keys - or key not in global_dict - ): - global_dict[key] = value - - elif isinstance(value, dict) and isinstance(global_dict[key], dict): - global_dict[key] = merge_overrides(global_dict[key], value) - - else: - global_dict[key] = value - return global_dict - - -def apply_overrides(global_presets, project_overrides): - global_presets = copy.deepcopy(global_presets) - if not project_overrides: - return global_presets - return merge_overrides(global_presets, project_overrides) - - -def project_presets(project_name=None, **kwargs): - global_presets = global_project_configurations(**kwargs) - - if not project_name: - project_name = os.environ.get("AVALON_PROJECT") - project_overrides = project_configurations_overrides( - project_name, **kwargs - ) - - return apply_overrides(global_presets, project_overrides) + return apply_overrides(studio_overrides, project_overrides) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py new file mode 100644 index 0000000000..4cd7203626 --- /dev/null +++ b/pype/configurations/lib.py @@ -0,0 +1,208 @@ +import os +import json +import logging +import copy + +log = logging.getLogger(__name__) + +# Metadata keys for work with studio and project overrides +OVERRIDEN_KEY = "__overriden_keys__" +# NOTE key popping not implemented yet +POP_KEY = "__pop_key__" + +# Folder where studio overrides are stored +STUDIO_OVERRIDES_PATH = os.environ["PYPE_CONFIG"] + +# File where studio's system overrides are stored +SYSTEM_CONFIGURATIONS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, "system_configurations.json" +) + +# File where studio's default project overrides are stored +PROJECT_CONFIGURATIONS_FILENAME = "project_configurations.json" +PROJECT_CONFIGURATIONS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, PROJECT_CONFIGURATIONS_FILENAME +) + +# Folder where studio's per project overrides are stored +STUDIO_PROJECT_OVERRIDES_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, "project_overrides" +) + +# Variable where cache of default configurations are stored +_DEFAULT_CONFIGURATIONS = None + + +def default_configuration(): + global _DEFAULT_CONFIGURATIONS + if _DEFAULT_CONFIGURATIONS is None: + current_dir = os.path.dirname(__file__) + defaults_path = os.path.join(current_dir, "defaults") + _DEFAULT_CONFIGURATIONS = load_jsons_from_dir(defaults_path) + return _DEFAULT_CONFIGURATIONS + + +def load_json(fpath): + # Load json data + with open(fpath, "r") as opened_file: + lines = opened_file.read().splitlines() + + # prepare json string + standard_json = "" + for line in lines: + # Remove all whitespace on both sides + line = line.strip() + + # Skip blank lines + if len(line) == 0: + continue + + standard_json += line + + # Check if has extra commas + extra_comma = False + if ",]" in standard_json or ",}" in standard_json: + extra_comma = True + standard_json = standard_json.replace(",]", "]") + standard_json = standard_json.replace(",}", "}") + + if extra_comma: + log.error("Extra comma in json file: \"{}\"".format(fpath)) + + # return empty dict if file is empty + if standard_json == "": + return {} + + # Try to parse string + try: + return json.loads(standard_json) + + except json.decoder.JSONDecodeError: + # Return empty dict if it is first time that decode error happened + return {} + + # Repreduce the exact same exception but traceback contains better + # information about position of error in the loaded json + try: + with open(fpath, "r") as opened_file: + json.load(opened_file) + + except json.decoder.JSONDecodeError: + log.warning( + "File has invalid json format \"{}\"".format(fpath), + exc_info=True + ) + + return {} + + +def subkey_merge(_dict, value, keys): + key = keys.pop(0) + if not keys: + _dict[key] = value + return _dict + + if key not in _dict: + _dict[key] = {} + _dict[key] = subkey_merge(_dict[key], value, keys) + + return _dict + + +def load_jsons_from_dir(path, *args, **kwargs): + output = {} + + path = os.path.normpath(path) + if not os.path.exists(path): + # TODO warning + return output + + sub_keys = list(kwargs.pop("subkeys", args)) + for sub_key in tuple(sub_keys): + _path = os.path.join(path, sub_key) + if not os.path.exists(_path): + break + + path = _path + sub_keys.pop(0) + + base_len = len(path) + 1 + for base, _directories, filenames in os.walk(path): + base_items_str = base[base_len:] + if not base_items_str: + base_items = [] + else: + base_items = base_items_str.split(os.path.sep) + + for filename in filenames: + basename, ext = os.path.splitext(filename) + if ext == ".json": + full_path = os.path.join(base, filename) + value = load_json(full_path) + dict_keys = base_items + [basename] + output = subkey_merge(output, value, dict_keys) + + for sub_key in sub_keys: + output = output[sub_key] + return output + + +def studio_system_configurations(): + if os.path.exists(SYSTEM_CONFIGURATIONS_PATH): + return load_json(SYSTEM_CONFIGURATIONS_PATH) + return {} + + +def studio_project_configurations(): + if os.path.exists(PROJECT_CONFIGURATIONS_PATH): + return load_json(PROJECT_CONFIGURATIONS_PATH) + return {} + + +def path_to_project_overrides(project_name): + return os.path.join( + STUDIO_PROJECT_OVERRIDES_PATH, + project_name, + PROJECT_CONFIGURATIONS_FILENAME + ) + + +def project_configurations_overrides(project_name): + if not project_name: + return {} + + path_to_json = path_to_project_overrides(project_name) + if not os.path.exists(path_to_json): + return {} + return load_json(path_to_json) + + +def merge_overrides(global_dict, override_dict): + if OVERRIDEN_KEY in override_dict: + overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) + else: + overriden_keys = set() + + for key, value in override_dict.items(): + if value == POP_KEY: + global_dict.pop(key) + + elif ( + key in overriden_keys + or key not in global_dict + ): + global_dict[key] = value + + elif isinstance(value, dict) and isinstance(global_dict[key], dict): + global_dict[key] = merge_overrides(global_dict[key], value) + + else: + global_dict[key] = value + return global_dict + + +def apply_overrides(global_presets, project_overrides): + global_presets = copy.deepcopy(global_presets) + if not project_overrides: + return global_presets + return merge_overrides(global_presets, project_overrides) From b6b439c369f164a9f4ed48bc232c283c0cf32564 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 15:36:36 +0200 Subject: [PATCH 808/947] config_value method renamed to studio_value --- .../config_setting/widgets/anatomy_inputs.py | 16 +++++++------- .../config_setting/widgets/base.py | 6 ++--- .../config_setting/widgets/inputs.py | 22 +++++++++---------- .../config_setting/widgets/widgets.py | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 43146f7442..18a58b1dc6 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -165,15 +165,15 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.discard_changes() def overrides(self): - return self.config_value(), True + return self.studio_value(), True def item_value(self): output = {} - output.update(self.root_widget.config_value()) - output.update(self.templates_widget.config_value()) + output.update(self.root_widget.studio_value()) + output.update(self.templates_widget.studio_value()) return output - def config_value(self): + def studio_value(self): return {self.key: self.item_value()} @@ -479,7 +479,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: return self.singleroot_widget.item_value() - def config_value(self): + def studio_value(self): return {self.key: self.item_value()} @@ -587,13 +587,13 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def overrides(self): if not self.child_overriden: return NOT_SET, False - return self.config_value(), True + return self.studio_value(), True def item_value(self): return self.value_input.item_value() - def config_value(self): - return self.value_input.config_value() + def studio_value(self): + return self.value_input.studio_value() TypeToKlass.types["anatomy"] = AnatomyWidget diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 55ca6096de..981713d5ae 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -125,7 +125,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = {} for item in self.input_fields: - all_values.update(item.config_value()) + all_values.update(item.studio_value()) for key in reversed(self.keys): _all_values = {key: all_values} @@ -499,7 +499,7 @@ class ProjectWidget(QtWidgets.QWidget): def _save_defaults(self): output = {} for item in self.input_fields: - output.update(item.config_value()) + output.update(item.studio_value()) for key in reversed(self.keys): _output = {key: output} @@ -507,7 +507,7 @@ class ProjectWidget(QtWidgets.QWidget): all_values = {} for item in self.input_fields: - all_values.update(item.config_value()) + all_values.update(item.studio_value()) for key in reversed(self.keys): _all_values = {key: all_values} diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b0aaa5ed3d..d1b71b2848 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -89,7 +89,7 @@ class ConfigObject(AbstractConfigObject): """Setter for global parent item to apply changes for all inputs.""" self._parent.ignore_value_changes = value - def config_value(self): + def studio_value(self): """Output for saving changes or overrides.""" return {self.key: self.item_value()} @@ -217,7 +217,7 @@ class InputObject(ConfigObject): def overrides(self): if not self.is_overriden: return NOT_SET, False - return self.config_value(), self.is_group + return self.studio_value(), self.is_group def hierarchical_style_update(self): self.update_style() @@ -899,7 +899,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def on_remove_clicked(self): self._parent.remove_row(self) - def config_value(self): + def studio_value(self): if self.value_input.isEnabled(): return self.value_input.item_value() return NOT_SET @@ -1106,7 +1106,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): def item_value(self): output = [] for item in self.input_fields: - value = item.config_value() + value = item.studio_value() if value is not NOT_SET: output.append(value) return output @@ -1245,7 +1245,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def row(self): return self._parent.input_fields.index(self) - def config_value(self): + def studio_value(self): key = self.key_input.text() value = self.value_input.item_value() return {key: value} @@ -1405,7 +1405,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def item_value(self): output = {} for item in self.input_fields: - output.update(item.config_value()) + output.update(item.studio_value()) return output def add_row(self, row=None, key=None, value=None, is_empty=False): @@ -1749,7 +1749,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) + output.update(input_field.studio_value()) return output def overrides(self): @@ -1859,7 +1859,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) + output.update(input_field.studio_value()) return output def _on_value_change(self, item=None): @@ -2236,7 +2236,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): output = {} for input_field in self.input_fields: - output.update(input_field.config_value()) + output.update(input_field.studio_value()) return output def overrides(self): @@ -2402,10 +2402,10 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts - output.update(input_field.config_value()) + output.update(input_field.studio_value()) return output - def config_value(self): + def studio_value(self): return self.item_value() def overrides(self): diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 5547f66597..1e57bf73df 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -344,7 +344,7 @@ class AbstractConfigObject: "Method `item_value` not implemented!" ) - def config_value(self): + def studio_value(self): """Output for saving changes or overrides.""" return {self.key: self.item_value()} From 778f645057f22e076c9b4e6cad4320b724069cb6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:07:05 +0200 Subject: [PATCH 809/947] renamed method studio_value back to config_value --- .../config_setting/widgets/anatomy_inputs.py | 16 +++++++------- .../config_setting/widgets/base.py | 6 ++--- .../config_setting/widgets/inputs.py | 22 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 18a58b1dc6..43146f7442 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -165,15 +165,15 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.discard_changes() def overrides(self): - return self.studio_value(), True + return self.config_value(), True def item_value(self): output = {} - output.update(self.root_widget.studio_value()) - output.update(self.templates_widget.studio_value()) + output.update(self.root_widget.config_value()) + output.update(self.templates_widget.config_value()) return output - def studio_value(self): + def config_value(self): return {self.key: self.item_value()} @@ -479,7 +479,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: return self.singleroot_widget.item_value() - def studio_value(self): + def config_value(self): return {self.key: self.item_value()} @@ -587,13 +587,13 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def overrides(self): if not self.child_overriden: return NOT_SET, False - return self.studio_value(), True + return self.config_value(), True def item_value(self): return self.value_input.item_value() - def studio_value(self): - return self.value_input.studio_value() + def config_value(self): + return self.value_input.config_value() TypeToKlass.types["anatomy"] = AnatomyWidget diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 981713d5ae..55ca6096de 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -125,7 +125,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = {} for item in self.input_fields: - all_values.update(item.studio_value()) + all_values.update(item.config_value()) for key in reversed(self.keys): _all_values = {key: all_values} @@ -499,7 +499,7 @@ class ProjectWidget(QtWidgets.QWidget): def _save_defaults(self): output = {} for item in self.input_fields: - output.update(item.studio_value()) + output.update(item.config_value()) for key in reversed(self.keys): _output = {key: output} @@ -507,7 +507,7 @@ class ProjectWidget(QtWidgets.QWidget): all_values = {} for item in self.input_fields: - all_values.update(item.studio_value()) + all_values.update(item.config_value()) for key in reversed(self.keys): _all_values = {key: all_values} diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index d1b71b2848..b0aaa5ed3d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -89,7 +89,7 @@ class ConfigObject(AbstractConfigObject): """Setter for global parent item to apply changes for all inputs.""" self._parent.ignore_value_changes = value - def studio_value(self): + def config_value(self): """Output for saving changes or overrides.""" return {self.key: self.item_value()} @@ -217,7 +217,7 @@ class InputObject(ConfigObject): def overrides(self): if not self.is_overriden: return NOT_SET, False - return self.studio_value(), self.is_group + return self.config_value(), self.is_group def hierarchical_style_update(self): self.update_style() @@ -899,7 +899,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def on_remove_clicked(self): self._parent.remove_row(self) - def studio_value(self): + def config_value(self): if self.value_input.isEnabled(): return self.value_input.item_value() return NOT_SET @@ -1106,7 +1106,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): def item_value(self): output = [] for item in self.input_fields: - value = item.studio_value() + value = item.config_value() if value is not NOT_SET: output.append(value) return output @@ -1245,7 +1245,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def row(self): return self._parent.input_fields.index(self) - def studio_value(self): + def config_value(self): key = self.key_input.text() value = self.value_input.item_value() return {key: value} @@ -1405,7 +1405,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): def item_value(self): output = {} for item in self.input_fields: - output.update(item.studio_value()) + output.update(item.config_value()) return output def add_row(self, row=None, key=None, value=None, is_empty=False): @@ -1749,7 +1749,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts - output.update(input_field.studio_value()) + output.update(input_field.config_value()) return output def overrides(self): @@ -1859,7 +1859,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts - output.update(input_field.studio_value()) + output.update(input_field.config_value()) return output def _on_value_change(self, item=None): @@ -2236,7 +2236,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): output = {} for input_field in self.input_fields: - output.update(input_field.studio_value()) + output.update(input_field.config_value()) return output def overrides(self): @@ -2402,10 +2402,10 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts - output.update(input_field.studio_value()) + output.update(input_field.config_value()) return output - def studio_value(self): + def config_value(self): return self.item_value() def overrides(self): From c483705a91e6cd9c4cf82634afdfc9428053022b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:16:22 +0200 Subject: [PATCH 810/947] any_parent_is_group is abstract attribute --- .../config_setting/widgets/inputs.py | 18 +++++++++++------- .../config_setting/widgets/widgets.py | 6 ++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index b0aaa5ed3d..9e712fd087 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -25,6 +25,7 @@ class ConfigObject(AbstractConfigObject): _is_group = False _is_nullable = False + _any_parent_is_group = None _log = None @property @@ -36,6 +37,10 @@ class ConfigObject(AbstractConfigObject): @property def has_studio_override(self): return self._has_studio_override + def any_parent_is_group(self): + if self._any_parent_is_group is None: + return super(ConfigObject, self).any_parent_is_group + return self._any_parent_is_group @property def is_modified(self): @@ -752,7 +757,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group + self._any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) @@ -843,7 +848,6 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def __init__(self, object_type, input_modifiers, config_parent, parent): self._parent = config_parent - super(ListItem, self).__init__(parent) layout = QtWidgets.QHBoxLayout(self) @@ -1280,7 +1284,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group + self._any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) @@ -1500,7 +1504,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group + self._any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) self._is_nullable = input_data.get("is_nullable", False) @@ -1789,7 +1793,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group + self._any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) @@ -1997,7 +2001,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group + self._any_parent_is_group = any_parent_is_group # This is partial input and dictionary input if not any_parent_is_group and not as_widget: @@ -2274,7 +2278,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group - self.any_parent_is_group = any_parent_is_group + self._any_parent_is_group = any_parent_is_group self._is_group = False diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index 1e57bf73df..d803624850 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -248,6 +248,12 @@ class AbstractConfigObject: "{} does not have implemented `is_overriden`".format(self) ) + @property + def any_parent_is_group(self): + raise NotImplementedError( + "{} does not have implemented `any_parent_is_group`".format(self) + ) + @property def was_overriden(self): """Initial state after applying overrides.""" From daebec1faec9fca76812ab8f9285849a25b66f19 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:16:33 +0200 Subject: [PATCH 811/947] fixe override key import --- pype/tools/config_setting/config_setting/widgets/lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index 69c3259b3b..6b3aa53c8f 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -1,9 +1,8 @@ import os import json import copy -from pype.api import config +from pype.configurations.lib import OVERRIDEN_KEY from queue import Queue -OVERRIDEN_KEY = config.OVERRIDEN_KEY # Singleton database of available inputs From ce90e8a1835cbef57ead0739bf980367b7b9d884 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:16:44 +0200 Subject: [PATCH 812/947] has_studio_override fix --- pype/tools/config_setting/config_setting/widgets/inputs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 9e712fd087..e7f3f71620 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -36,7 +36,8 @@ class ConfigObject(AbstractConfigObject): @property def has_studio_override(self): - return self._has_studio_override + return self._has_studio_override or self._parent.has_studio_override + def any_parent_is_group(self): if self._any_parent_is_group is None: return super(ConfigObject, self).any_parent_is_group @@ -290,7 +291,7 @@ class InputObject(ConfigObject): @property def child_has_studio_override(self): - return self.has_studio_override + return self._has_studio_override @property def child_modified(self): From 39cde9bd82e6d3852f7d92550a10e2884ba05e95 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:34:46 +0200 Subject: [PATCH 813/947] update saving of studio overrides --- .../config_setting/widgets/base.py | 146 +++++------------- 1 file changed, 40 insertions(+), 106 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 55ca6096de..9abe05946e 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -1,7 +1,15 @@ import os import json from Qt import QtWidgets, QtCore, QtGui -from pype.api import config +from pype.configurations.lib import ( + SYSTEM_CONFIGURATIONS_PATH, + PROJECT_CONFIGURATIONS_PATH, + default_configuration, + studio_system_configurations, + project_configurations_overrides, + path_to_project_overrides, + studio_project_configurations +) from .widgets import UnsavedChangesDialog from . import lib from avalon import io @@ -123,60 +131,31 @@ class SystemWidget(QtWidgets.QWidget): first_invalid_item.setFocus(True) return - all_values = {} - for item in self.input_fields: - all_values.update(item.config_value()) + _data = {} + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not lib.NOT_SET: + _data.update(value) - for key in reversed(self.keys): - _all_values = {key: all_values} - all_values = _all_values + values = _data["system"] - # Skip first key - all_values = all_values["system"] + dirpath = os.path.dirname(SYSTEM_CONFIGURATIONS_PATH) + if not os.path.exists(dirpath): + os.makedirs(dirpath) - # Load studio data with metadata - current_configurations = config.system_configurations() - - keys_to_file = lib.file_keys_from_schema(self.schema) - for key_sequence in keys_to_file: - # Skip first key - key_sequence = key_sequence[1:] - subpath = "/".join(key_sequence) + ".json" - origin_values = current_configurations - for key in key_sequence: - if not origin_values or key not in origin_values: - origin_values = {} - break - origin_values = origin_values[key] - - if not origin_values: - origin_values = {} - - new_values = all_values - for key in key_sequence: - new_values = new_values[key] - origin_values.update(new_values) - raise NotImplementedError("Output from global values has changed") - output_path = os.path.join( - config.STUDIO_PRESETS_PATH, subpath - ) - dirpath = os.path.dirname(output_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving data to: ", output_path) - with open(output_path, "w") as file_stream: - json.dump(origin_values, file_stream, indent=4) + print("Saving data to:", SYSTEM_CONFIGURATIONS_PATH) + with open(SYSTEM_CONFIGURATIONS_PATH, "w") as file_stream: + json.dump(values, file_stream, indent=4) self._update_values() def _update_values(self): - default_values = config.default_configuration() + default_values = default_configuration() default_values = {"system": default_values["system_configurations"]} for input_field in self.input_fields: input_field.update_default_values(default_values) - system_values = {"system": config.system_configurations()} + system_values = {"system": studio_system_configurations()} for input_field in self.input_fields: input_field.update_studio_values(system_values) @@ -429,7 +408,7 @@ class ProjectWidget(QtWidgets.QWidget): _overrides = lib.NOT_SET self.is_overidable = False else: - _overrides = config.project_configurations_overrides(project_name) + _overrides = project_configurations_overrides(project_name) self.is_overidable = True overrides = {"project": lib.convert_overrides_to_gui_data(_overrides)} @@ -475,94 +454,49 @@ class ProjectWidget(QtWidgets.QWidget): value, is_group = item.overrides() if value is not lib.NOT_SET: _data.update(value) - if is_group: - raise Exception( - "Top item can't be overriden in Project widget." - ) data = _data.get("project") or {} output_data = lib.convert_gui_data_to_overrides(data) - overrides_json_path = config.path_to_project_overrides( + overrides_json_path = path_to_project_overrides( self.project_name ) dirpath = os.path.dirname(overrides_json_path) if not os.path.exists(dirpath): os.makedirs(dirpath) - print("Saving data to: ", overrides_json_path) + print("Saving data to:", overrides_json_path) with open(overrides_json_path, "w") as file_stream: json.dump(output_data, file_stream, indent=4) self._on_project_change() def _save_defaults(self): - output = {} - for item in self.input_fields: - output.update(item.config_value()) + _data = {} + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not lib.NOT_SET: + _data.update(value) - for key in reversed(self.keys): - _output = {key: output} - output = _output + output = _data["project"] - all_values = {} - for item in self.input_fields: - all_values.update(item.config_value()) + dirpath = os.path.dirname(PROJECT_CONFIGURATIONS_PATH) + if not os.path.exists(dirpath): + os.makedirs(dirpath) - for key in reversed(self.keys): - _all_values = {key: all_values} - all_values = _all_values - - # Skip first key - all_values = all_values["project"] - - # Load studio data with metadata - current_configurations = config.global_project_configurations() - - keys_to_file = lib.file_keys_from_schema(self.schema) - for key_sequence in keys_to_file: - # Skip first key - key_sequence = key_sequence[1:] - subpath = "/".join(key_sequence) + ".json" - origin_values = current_configurations - for key in key_sequence: - if not origin_values or key not in origin_values: - origin_values = {} - break - origin_values = origin_values[key] - - if not origin_values: - origin_values = {} - - new_values = all_values - for key in key_sequence: - new_values = new_values[key] - if isinstance(new_values, dict): - origin_values.update(new_values) - else: - origin_values = new_values - - raise NotImplementedError("Output from global values has changed") - output_path = os.path.join( - config.PROJECT_PRESETS_PATH, subpath - ) - dirpath = os.path.dirname(output_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - print("Saving data to: ", output_path) - with open(output_path, "w") as file_stream: - json.dump(origin_values, file_stream, indent=4) + print("Saving data to:", PROJECT_CONFIGURATIONS_PATH) + with open(PROJECT_CONFIGURATIONS_PATH, "w") as file_stream: + json.dump(output, file_stream, indent=4) self._update_values() def _update_values(self): - default_values = config.default_configuration() + default_values = default_configuration() default_values = {"project": default_values["project_configurations"]} for input_field in self.input_fields: input_field.update_default_values(default_values) - studio_values = {"project": config.global_project_configurations()} + studio_values = {"project": studio_project_configurations()} for input_field in self.input_fields: input_field.update_studio_values(studio_values) From 9cda39766cb7e8361b5f1c9db41bb973ab9acac3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:35:01 +0200 Subject: [PATCH 814/947] inputs has studio_overrides methods now --- .../config_setting/widgets/inputs.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index e7f3f71620..6e229fb9ea 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -220,6 +220,11 @@ class InputObject(ConfigObject): self._is_modified = False + def studio_overrides(self): + if not self.has_studio_override: + return NOT_SET, False + return self.config_value(), self.is_group + def overrides(self): if not self.is_overriden: return NOT_SET, False @@ -1757,6 +1762,22 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): output.update(input_field.config_value()) return output + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False @@ -1953,6 +1974,22 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): ) self._was_overriden = bool(self._is_overriden) + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False @@ -2244,6 +2281,15 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): output.update(input_field.config_value()) return output + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + value = self.item_value() + if not self.multiplatform: + value = {self.key: value} + return value, self.is_group + def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False @@ -2413,6 +2459,22 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): def config_value(self): return self.item_value() + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return values, self.is_group + def overrides(self): if not self.is_overriden and not self.child_overriden: return NOT_SET, False From 9521ea3f08ee69b532c1991ac82d644404483b7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:40:40 +0200 Subject: [PATCH 815/947] removed attribute methods from base widgets --- .../config_setting/widgets/base.py | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 9abe05946e..172450684b 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -17,9 +17,10 @@ from avalon import io class SystemWidget(QtWidgets.QWidget): is_overidable = False - _is_overriden = False - _is_group = False - _any_parent_is_group = False + has_studio_override = False + is_overriden = False + is_group = False + any_parent_is_group = False def __init__(self, parent=None): super(SystemWidget, self).__init__(parent) @@ -67,18 +68,6 @@ class SystemWidget(QtWidgets.QWidget): def any_parent_overriden(self): return False - @property - def is_overriden(self): - return self._is_overriden - - @property - def is_group(self): - return self._is_group - - @property - def any_parent_is_group(self): - return self._any_parent_is_group - @property def ignore_value_changes(self): return self._ignore_value_changes @@ -297,9 +286,10 @@ class ProjectListWidget(QtWidgets.QWidget): class ProjectWidget(QtWidgets.QWidget): - _is_overriden = False - _is_group = False - _any_parent_is_group = False + has_studio_override = False + is_overriden = False + is_group = False + any_parent_is_group = False def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) @@ -362,18 +352,6 @@ class ProjectWidget(QtWidgets.QWidget): def any_parent_overriden(self): return False - @property - def is_overriden(self): - return self._is_overriden - - @property - def is_group(self): - return self._is_group - - @property - def any_parent_is_group(self): - return self._any_parent_is_group - @property def ignore_value_changes(self): return self._ignore_value_changes From 9693a6a37b886e1ae3646f52903d493532542b85 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 16:40:58 +0200 Subject: [PATCH 816/947] any parent is group is attribute --- pype/tools/config_setting/config_setting/widgets/inputs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 6e229fb9ea..3a3d517cc1 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -38,6 +38,7 @@ class ConfigObject(AbstractConfigObject): def has_studio_override(self): return self._has_studio_override or self._parent.has_studio_override + @property def any_parent_is_group(self): if self._any_parent_is_group is None: return super(ConfigObject, self).any_parent_is_group From ffa233994e58994192e2715b1acbedea4f3c45ca Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Sep 2020 16:57:19 +0200 Subject: [PATCH 817/947] bump version --- pype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/version.py b/pype/version.py index 9e1a271244..95a6d3a792 100644 --- a/pype/version.py +++ b/pype/version.py @@ -1 +1 @@ -__version__ = "2.11.8" +__version__ = "2.12.0" From d51a5701590c08fcb928f784480d73da29468489 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 9 Sep 2020 17:27:16 +0200 Subject: [PATCH 818/947] configure changelog generator --- .github_changelog_generator | 7 + HISTORY.md | 435 ++++++++++++++++++++++++++++++++++++ changelog.md | 120 ---------- 3 files changed, 442 insertions(+), 120 deletions(-) create mode 100644 .github_changelog_generator create mode 100644 HISTORY.md delete mode 100644 changelog.md diff --git a/.github_changelog_generator b/.github_changelog_generator new file mode 100644 index 0000000000..93ab39f4d0 --- /dev/null +++ b/.github_changelog_generator @@ -0,0 +1,7 @@ +pr-wo-labels=False +exclude-labels=duplicate,question,invalid,wontfix,weekly-digest +author=False +unreleased=False +since-tag=2.11.0 +release-branch=master +enhancement-label=New / Enhancements diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000000..4b5463c924 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,435 @@ +# Pype changelog # +Welcome to pype changelog + + +## 2.11.0 ## + +_**release date:** 27 July 2020_ + +**new:** +- _(blender)_ namespace support [\#341](https://github.com/pypeclub/pype/pull/341) +- _(blender)_ start end frames [\#330](https://github.com/pypeclub/pype/pull/330) +- _(blender)_ camera asset [\#322](https://github.com/pypeclub/pype/pull/322) +- _(pype)_ toggle instances per family in pyblish GUI [\#320](https://github.com/pypeclub/pype/pull/320) +- _(pype)_ current release version is now shown in the tray menu [#379](https://github.com/pypeclub/pype/pull/379) + + +**improved:** +- _(resolve)_ tagging for publish [\#239](https://github.com/pypeclub/pype/issues/239) +- _(pype)_ Support publishing a subset of shots with standalone editorial [\#336](https://github.com/pypeclub/pype/pull/336) +- _(harmony)_ Basic support for palettes [\#324](https://github.com/pypeclub/pype/pull/324) +- _(photoshop)_ Flag outdated containers on startup and publish. [\#309](https://github.com/pypeclub/pype/pull/309) +- _(harmony)_ Flag Outdated containers [\#302](https://github.com/pypeclub/pype/pull/302) +- _(photoshop)_ Publish review [\#298](https://github.com/pypeclub/pype/pull/298) +- _(pype)_ Optional Last workfile launch [\#365](https://github.com/pypeclub/pype/pull/365) + + +**fixed:** +- _(premiere)_ workflow fixes [\#346](https://github.com/pypeclub/pype/pull/346) +- _(pype)_ pype-setup does not work with space in path [\#327](https://github.com/pypeclub/pype/issues/327) +- _(ftrack)_ Ftrack delete action cause circular error [\#206](https://github.com/pypeclub/pype/issues/206) +- _(nuke)_ Priority was forced to 50 [\#345](https://github.com/pypeclub/pype/pull/345) +- _(nuke)_ Fix ValidateNukeWriteKnobs [\#340](https://github.com/pypeclub/pype/pull/340) +- _(maya)_ If camera attributes are connected, we can ignore them. [\#339](https://github.com/pypeclub/pype/pull/339) +- _(pype)_ stop appending of tools environment to existing env [\#337](https://github.com/pypeclub/pype/pull/337) +- _(ftrack)_ Ftrack timeout needs to look at AVALON\_TIMEOUT [\#325](https://github.com/pypeclub/pype/pull/325) +- _(harmony)_ Only zip files are supported. [\#310](https://github.com/pypeclub/pype/pull/310) +- _(pype)_ hotfix/Fix event server mongo uri [\#305](https://github.com/pypeclub/pype/pull/305) +- _(photoshop)_ Subset was not named or validated correctly. [\#304](https://github.com/pypeclub/pype/pull/304) + + + + +## 2.10.0 ## + +_**release date:** 17 June 2020_ + +**new:** +- _(harmony)_ **Toon Boom Harmony** has been greatly extended to support rigging, scene build, animation and rendering workflows. [#270](https://github.com/pypeclub/pype/issues/270) [#271](https://github.com/pypeclub/pype/issues/271) [#190](https://github.com/pypeclub/pype/issues/190) [#191](https://github.com/pypeclub/pype/issues/191) [#172](https://github.com/pypeclub/pype/issues/172) [#168](https://github.com/pypeclub/pype/issues/168) +- _(pype)_ Added support for rudimentary **edl publishing** into individual shots. [#265](https://github.com/pypeclub/pype/issues/265) +- _(celaction)_ Simple **Celaction** integration has been added with support for workfiles and rendering. [#255](https://github.com/pypeclub/pype/issues/255) +- _(maya)_ Support for multiple job types when submitting to the farm. We can now render Maya or Standalone render jobs for Vray and Arnold (limited support for arnold) [#204](https://github.com/pypeclub/pype/issues/204) +- _(photoshop)_ Added initial support for Photoshop [#232](https://github.com/pypeclub/pype/issues/232) + +**improved:** +- _(blender)_ Updated support for rigs and added support Layout family [#233](https://github.com/pypeclub/pype/issues/233) [#226](https://github.com/pypeclub/pype/issues/226) +- _(premiere)_ It is now possible to choose different storage root for workfiles of different task types. [#255](https://github.com/pypeclub/pype/issues/255) +- _(maya)_ Support for unmerged AOVs in Redshift multipart EXRs [#197](https://github.com/pypeclub/pype/issues/197) +- _(pype)_ Pype repository has been refactored in preparation for 3.0 release [#169](https://github.com/pypeclub/pype/issues/169) +- _(deadline)_ All file dependencies are now passed to deadline from maya to prevent premature start of rendering if caches or textures haven't been coppied over yet. [#195](https://github.com/pypeclub/pype/issues/195) +- _(nuke)_ Script validation can now be made optional. [#194](https://github.com/pypeclub/pype/issues/194) +- _(pype)_ Publishing can now be stopped at any time. [#194](https://github.com/pypeclub/pype/issues/194) + +**fix:** +- _(pype)_ Pyblish-lite has been integrated into pype repository, plus various publishing GUI fixes. [#274](https://github.com/pypeclub/pype/issues/274) [#275](https://github.com/pypeclub/pype/issues/275) [#268](https://github.com/pypeclub/pype/issues/268) [#227](https://github.com/pypeclub/pype/issues/227) [#238](https://github.com/pypeclub/pype/issues/238) +- _(maya)_ Alembic extractor was getting wrong frame range type in certain scenarios [#254](https://github.com/pypeclub/pype/issues/254) +- _(maya)_ Attaching a render to subset in maya was not passing validation in certain scenarios [#256](https://github.com/pypeclub/pype/issues/256) +- _(ftrack)_ Various small fixes to ftrack sync [#263](https://github.com/pypeclub/pype/issues/263) [#259](https://github.com/pypeclub/pype/issues/259) +- _(maya)_ Look extraction is now able to skp invalid connections in shaders [#207](https://github.com/pypeclub/pype/issues/207) + + + + +## 2.9.0 ## + +_**release date:** 25 May 2020_ + +**new:** +- _(pype)_ Support for **Multiroot projects**. You can now store project data on multiple physical or virtual storages and target individual publishes to these locations. For instance render can be stored on a faster storage than the rest of the project. [#145](https://github.com/pypeclub/pype/issues/145), [#38](https://github.com/pypeclub/pype/issues/38) +- _(harmony)_ Basic implementation of **Toon Boom Harmony** has been added. [#142](https://github.com/pypeclub/pype/issues/142) +- _(pype)_ OSX support is in public beta now. There are issues to be expected, but the main implementation should be functional. [#141](https://github.com/pypeclub/pype/issues/141) + + +**improved:** + +- _(pype)_ **Review extractor** has been completely rebuilt. It now supports granular filtering so you can create **multiple outputs** for different tasks, families or hosts. [#103](https://github.com/pypeclub/pype/issues/103), [#166](https://github.com/pypeclub/pype/issues/166), [#165](https://github.com/pypeclub/pype/issues/165) +- _(pype)_ **Burnin** generation had been extended to **support same multi-output filtering** as review extractor [#103](https://github.com/pypeclub/pype/issues/103) +- _(pype)_ Publishing file templates can now be specified in config for each individual family [#114](https://github.com/pypeclub/pype/issues/114) +- _(pype)_ Studio specific plugins can now be appended to pype standard publishing plugins. [#112](https://github.com/pypeclub/pype/issues/112) +- _(nukestudio)_ Reviewable clips no longer need to be previously cut, exported and re-imported to timeline. **Pype can now dynamically cut reviewable quicktimes** from continuous offline footage during publishing. [#23](https://github.com/pypeclub/pype/issues/23) +- _(deadline)_ Deadline can now correctly differentiate between staging and production pype. [#154](https://github.com/pypeclub/pype/issues/154) +- _(deadline)_ `PYPE_PYTHON_EXE` env variable can now be used to direct publishing to explicit python installation. [#120](https://github.com/pypeclub/pype/issues/120) +- _(nuke)_ Nuke now check for new version of loaded data on file open. [#140](https://github.com/pypeclub/pype/issues/140) +- _(nuke)_ frame range and limit checkboxes are now exposed on write node. [#119](https://github.com/pypeclub/pype/issues/119) + + + +**fix:** + +- _(nukestudio)_ Project Location was using backslashes which was breaking nukestudio native exporting in certains configurations [#82](https://github.com/pypeclub/pype/issues/82) +- _(nukestudio)_ Duplicity in hierarchy tags was prone to throwing publishing error [#130](https://github.com/pypeclub/pype/issues/130), [#144](https://github.com/pypeclub/pype/issues/144) +- _(ftrack)_ multiple stability improvements [#157](https://github.com/pypeclub/pype/issues/157), [#159](https://github.com/pypeclub/pype/issues/159), [#128](https://github.com/pypeclub/pype/issues/128), [#118](https://github.com/pypeclub/pype/issues/118), [#127](https://github.com/pypeclub/pype/issues/127) +- _(deadline)_ multipart EXRs were stopping review publishing on the farm. They are still not supported for automatic review generation, but the publish will go through correctly without the quicktime. [#155](https://github.com/pypeclub/pype/issues/155) +- _(deadline)_ If deadline is non-responsive it will no longer freeze host when publishing [#149](https://github.com/pypeclub/pype/issues/149) +- _(deadline)_ Sometimes deadline was trying to launch render before all the source data was coppied over. [#137](https://github.com/pypeclub/pype/issues/137) _(harmony)_ Basic implementation of **Toon Boom Harmony** has been added. [#142](https://github.com/pypeclub/pype/issues/142) +- _(nuke)_ Filepath knob wasn't updated properly. [#131](https://github.com/pypeclub/pype/issues/131) +- _(maya)_ When extracting animation, the "Write Color Set" options on the instance were not respected. [#108](https://github.com/pypeclub/pype/issues/108) +- _(maya)_ Attribute overrides for AOV only worked for the legacy render layers. Now it works for new render setup as well [#132](https://github.com/pypeclub/pype/issues/132) +- _(maya)_ Stability and usability improvements in yeti workflow [#104](https://github.com/pypeclub/pype/issues/104) + + + + +## 2.8.0 ## + +_**release date:** 20 April 2020_ + +**new:** + +- _(pype)_ Option to generate slates from json templates. [PYPE-628] [#26](https://github.com/pypeclub/pype/issues/26) +- _(pype)_ It is now possible to automate loading of published subsets into any scene. Documentation will follow :). [PYPE-611] [#24](https://github.com/pypeclub/pype/issues/24) + +**fix:** + +- _(maya)_ Some Redshift render tokens could break publishing. [PYPE-778] [#33](https://github.com/pypeclub/pype/issues/33) +- _(maya)_ Publish was not preserving maya file extension. [#39](https://github.com/pypeclub/pype/issues/39) +- _(maya)_ Rig output validator was failing on nodes without shapes. [#40](https://github.com/pypeclub/pype/issues/40) +- _(maya)_ Yeti caches can now be properly versioned up in the scene inventory. [#40](https://github.com/pypeclub/pype/issues/40) +- _(nuke)_ Build first workfiles was not accepting jpeg sequences. [#34](https://github.com/pypeclub/pype/issues/34) +- _(deadline)_ Trying to generate ffmpeg review from multipart EXRs no longer crashes publishing. [PYPE-781] +- _(deadline)_ Render publishing is more stable in multiplatform environments. [PYPE-775] + + + + +## 2.7.0 ## + +_**release date:** 30 March 2020_ + +**new:** + +- _(maya)_ Artist can now choose to load multiple references of the same subset at once [PYPE-646, PYPS-81] +- _(nuke)_ Option to use named OCIO colorspaces for review colour baking. [PYPS-82] +- _(pype)_ Pype can now work with `master` versions for publishing and loading. These are non-versioned publishes that are overwritten with the latest version during publish. These are now supported in all the GUIs, but their publishing is deactivated by default. [PYPE-653] +- _(blender)_ Added support for basic blender workflow. We currently support `rig`, `model` and `animation` families. [PYPE-768] +- _(pype)_ Source timecode can now be used in burn-ins. [PYPE-777] +- _(pype)_ Review outputs profiles can now specify delivery resolution different than project setting [PYPE-759] +- _(nuke)_ Bookmark to current context is now added automatically to all nuke browser windows. [PYPE-712] + +**change:** + +- _(maya)_ It is now possible to publish camera without. baking. Keep in mind that unbaked cameras can't be guaranteed to work in other hosts. [PYPE-595] +- _(maya)_ All the renders from maya are now grouped in the loader by their Layer name. [PYPE-482] +- _(nuke/hiero)_ Any publishes from nuke and hiero can now be versioned independently of the workfile. [PYPE-728] + + +**fix:** + +- _(nuke)_ Mixed slashes caused issues in ocio config path. +- _(pype)_ Intent field in pyblish GUI was passing label instead of value to ftrack. [PYPE-733] +- _(nuke)_ Publishing of pre-renders was inconsistent. [PYPE-766] +- _(maya)_ Handles and frame ranges were inconsistent in various places during publishing. +- _(nuke)_ Nuke was crashing if it ran into certain missing knobs. For example DPX output missing `autocrop` [PYPE-774] +- _(deadline)_ Project overrides were not working properly with farm render publishing. +- _(hiero)_ Problems with single frame plates publishing. +- _(maya)_ Redshift RenderPass token were breaking render publishing. [PYPE-778] +- _(nuke)_ Build first workfile was not accepting jpeg sequences. +- _(maya)_ Multipart (Multilayer) EXRs were breaking review publishing due to FFMPEG incompatiblity [PYPE-781] + + + +## 2.6.0 ## + +_**release date:** 9 March 2020_ + +**change:** +- _(maya)_ render publishing has been simplified and made more robust. Render setup layers are now automatically added to publishing subsets and `render globals` family has been replaced with simple `render` [PYPE-570] +- _(avalon)_ change context and workfiles apps, have been merged into one, that allows both actions to be performed at the same time. [PYPE-747] +- _(pype)_ thumbnails are now automatically propagate to asset from the last published subset in the loader +- _(ftrack)_ publishing comment and intent are now being published to ftrack note as well as describtion. [PYPE-727] +- _(pype)_ when overriding existing version new old representations are now overriden, instead of the new ones just being appended. (to allow this behaviour, the version validator need to be disabled. [PYPE-690]) +- _(pype)_ burnin preset has been significantly simplified. It now doesn't require passing function to each field, but only need the actual text template. to use this, all the current burnin PRESETS MUST BE UPDATED for all the projects. +- _(ftrack)_ credentials are now stored on a per server basis, so it's possible to switch between ftrack servers without having to log in and out. [PYPE-723] + + +**new:** +- _(pype)_ production and development deployments now have different colour of the tray icon. Orange for Dev and Green for production [PYPE-718] +- _(maya)_ renders can now be attached to a publishable subset rather than creating their own subset. For example it is possible to create a reviewable `look` or `model` render and have it correctly attached as a representation of the subsets [PYPE-451] +- _(maya)_ after saving current scene into a new context (as a new shot for instance), all the scene publishing subsets data gets re-generated automatically to match the new context [PYPE-532] +- _(pype)_ we now support project specific publish, load and create plugins [PYPE-740] +- _(ftrack)_ new action that allow archiving/deleting old published versions. User can keep how many of the latest version to keep when the action is ran. [PYPE-748, PYPE-715] +- _(ftrack)_ it is now possible to monitor and restart ftrack event server using ftrack action. [PYPE-658] +- _(pype)_ validator that prevent accidental overwrites of previously published versions. [PYPE-680] +- _(avalon)_ avalon core updated to version 5.6.0 +- _(maya)_ added validator to make sure that relative paths are used when publishing arnold standins. +- _(nukestudio)_ it is now possible to extract and publish audio family from clip in nuke studio [PYPE-682] + +**fix**: +- _(maya)_ maya set framerange button was ignoring handles [PYPE-719] +- _(ftrack)_ sync to avalon was sometime crashing when ran on empty project +- _(nukestudio)_ publishing same shots after they've been previously archived/deleted would result in a crash. [PYPE-737] +- _(nuke)_ slate workflow was breaking in certain scenarios. [PYPE-730] +- _(pype)_ rendering publish workflow has been significantly improved to prevent error resulting from implicit render collection. [PYPE-665, PYPE-746] +- _(pype)_ launching application on a non-synced project resulted in obscure [PYPE-528] +- _(pype)_ missing keys in burnins no longer result in an error. [PYPE-706] +- _(ftrack)_ create folder structure action was sometimes failing for project managers due to wrong permissions. +- _(Nukestudio)_ using `source` in the start frame tag could result in wrong frame range calculation +- _(ftrack)_ sync to avalon action and event have been improved by catching more edge cases and provessing them properly. + + + +## 2.5.0 ## + +_**release date:** 11 Feb 2020_ + +**change:** +- _(pype)_ added many logs for easier debugging +- _(pype)_ review presets can now be separated between 2d and 3d renders [PYPE-693] +- _(pype)_ anatomy module has been greatly improved to allow for more dynamic pulblishing and faster debugging [PYPE-685] +- _(pype)_ avalon schemas have been moved from `pype-config` to `pype` repository, for simplification. [PYPE-670] +- _(ftrack)_ updated to latest ftrack API +- _(ftrack)_ publishing comments now appear in ftrack also as a note on version with customisable category [PYPE-645] +- _(ftrack)_ delete asset/subset action had been improved. It is now able to remove multiple entities and descendants of the selected entities [PYPE-361, PYPS-72] +- _(workfiles)_ added date field to workfiles app [PYPE-603] +- _(maya)_ old deprecated loader have been removed in favour of a single unified reference loader (old scenes will upgrade automatically to the new loader upon opening) [PYPE-633, PYPE-697] +- _(avalon)_ core updated to 5.5.15 [PYPE-671] +- _(nuke)_ library loader is now available in nuke [PYPE-698] + + +**new:** +- _(pype)_ added pype render wrapper to allow rendering on mixed platform farms. [PYPE-634] +- _(pype)_ added `pype launch` command. It let's admin run applications with dynamically built environment based on the given context. [PYPE-634] +- _(pype)_ added support for extracting review sequences with burnins [PYPE-657] +- _(publish)_ users can now set intent next to a comment when publishing. This will then be reflected on an attribute in ftrack. [PYPE-632] +- _(burnin)_ timecode can now be added to burnin +- _(burnin)_ datetime keys can now be added to burnin and anatomy [PYPE-651] +- _(burnin)_ anatomy templates can now be used in burnins. [PYPE=626] +- _(nuke)_ new validator for render resolution +- _(nuke)_ support for attach slate to nuke renders [PYPE-630] +- _(nuke)_ png sequences were added to loaders +- _(maya)_ added maya 2020 compatibility [PYPE-677] +- _(maya)_ ability to publish and load .ASS standin sequences [PYPS-54] +- _(pype)_ thumbnails can now be published and are visible in the loader. `AVALON_THUMBNAIL_ROOT` environment variable needs to be set for this to work [PYPE-573, PYPE-132] +- _(blender)_ base implementation of blender was added with publishing and loading of .blend files [PYPE-612] +- _(ftrack)_ new action for preparing deliveries [PYPE-639] + + +**fix**: +- _(burnin)_ more robust way of finding ffmpeg for burnins. +- _(pype)_ improved UNC paths remapping when sending to farm. +- _(pype)_ float frames sometimes made their way to representation context in database, breaking loaders [PYPE-668] +- _(pype)_ `pype install --force` was failing sometimes [PYPE-600] +- _(pype)_ padding in published files got calculated wrongly sometimes. It is now instead being always read from project anatomy. [PYPE-667] +- _(publish)_ comment publishing was failing in certain situations +- _(ftrack)_ multiple edge case scenario fixes in auto sync and sync-to-avalon action +- _(ftrack)_ sync to avalon now works on empty projects +- _(ftrack)_ thumbnail update event was failing when deleting entities [PYPE-561] +- _(nuke)_ loader applies proper colorspaces from Presets +- _(nuke)_ publishing handles didn't always work correctly [PYPE-686] +- _(maya)_ assembly publishing and loading wasn't working correctly + + + + + +## 2.4.0 ## + +_**release date:** 9 Dec 2019_ + +**change:** +- _(ftrack)_ version to status ftrack event can now be configured from Presets + - based on preset `presets/ftracc/ftrack_config.json["status_version_to_task"]` +- _(ftrack)_ sync to avalon event has been completely re-written. It now supports most of the project management situations on ftrack including moving, renaming and deleting entities, updating attributes and working with tasks. +- _(ftrack)_ sync to avalon action has been also re-writen. It is now much faster (up to 100 times depending on a project structure), has much better logging and reporting on encountered problems, and is able to handle much more complex situations. +- _(ftrack)_ sync to avalon trigger by checking `auto-sync` toggle on ftrack [PYPE-504] +- _(pype)_ various new features in the REST api +- _(pype)_ new visual identity used across pype +- _(pype)_ started moving all requirements to pip installation rather than vendorising them in pype repository. Due to a few yet unreleased packages, this means that pype can temporarily be only installed in the offline mode. + +**new:** +- _(nuke)_ support for publishing gizmos and loading them as viewer processes +- _(nuke)_ support for publishing nuke nodes from backdrops and loading them back +- _(pype)_ burnins can now work with start and end frames as keys + - use keys `{frame_start}`, `{frame_end}` and `{current_frame}` in burnin preset to use them. [PYPS-44,PYPS-73, PYPE-602] +- _(pype)_ option to filter logs by user and level in loggin GUI +- _(pype)_ image family added to standalone publisher [PYPE-574] +- _(pype)_ matchmove family added to standalone publisher [PYPE-574] +- _(nuke)_ validator for comparing arbitrary knobs with values from presets +- _(maya)_ option to force maya to copy textures in the new look publish rather than hardlinking them +- _(pype)_ comments from pyblish GUI are now being added to ftrack version +- _(maya)_ validator for checking outdated containers in the scene +- _(maya)_ option to publish and load arnold standin sequence [PYPE-579, PYPS-54] + +**fix**: +- _(pype)_ burnins were not respecting codec of the input video +- _(nuke)_ lot's of various nuke and nuke studio fixes across the board [PYPS-45] +- _(pype)_ workfiles app is not launching with the start of the app by default [PYPE-569] +- _(ftrack)_ ftrack integration during publishing was failing under certain situations [PYPS-66] +- _(pype)_ minor fixes in REST api +- _(ftrack)_ status change event was crashing when the target status was missing [PYPS-68] +- _(ftrack)_ actions will try to reconnect if they fail for some reason +- _(maya)_ problems with fps mapping when using float FPS values +- _(deadline)_ overall improvements to deadline publishing +- _(setup)_ environment variables are now remapped on the fly based on the platform pype is running on. This fixes many issues in mixed platform environments. + + + +## 2.3.6 # + +_**release date:** 27 Nov 2019_ + +**hotfix**: +- _(ftrack)_ was hiding important debug logo +- _(nuke)_ crashes during workfile publishing +- _(ftrack)_ event server crashes because of signal problems +- _(muster)_ problems with muster render submissions +- _(ftrack)_ thumbnail update event syntax errors + + +## 2.3.0 ## +_release date: 6 Oct 2019_ + +**new**: +- _(maya)_ support for yeti rigs and yeti caches +- _(maya)_ validator for comparing arbitrary attributes against ftrack +- _(pype)_ burnins can now show current date and time +- _(muster)_ pools can now be set in render globals in maya +- _(pype)_ Rest API has been implemented in beta stage +- _(nuke)_ LUT loader has been added +- _(pype)_ rudimentary user module has been added as preparation for user management +- _(pype)_ a simple logging GUI has been added to pype tray +- _(nuke)_ nuke can now bake input process into mov +- _(maya)_ imported models now have selection handle displayed by defaulting +- _(avalon)_ it's is now possible to load multiple assets at once using loader +- _(maya)_ added ability to automatically connect yeti rig to a mesh upon loading + +**changed**: +- _(ftrack)_ event server now runs two parallel processes and is able to keep queue of events to process. +- _(nuke)_ task name is now added to all rendered subsets +- _(pype)_ adding more families to standalone publisher +- _(pype)_ standalone publisher now uses pyblish-lite +- _(pype)_ standalone publisher can now create review quicktimes +- _(ftrack)_ queries to ftrack were sped up +- _(ftrack)_ multiple ftrack action have been deprecated +- _(avalon)_ avalon upstream has been updated to 5.5.0 +- _(nukestudio)_ published transforms can now be animated +- + +**fix**: +- _(maya)_ fps popup button didn't work in some cases +- _(maya)_ geometry instances and references in maya were losing shader assignments +- _(muster)_ muster rendering templates were not working correctly +- _(maya)_ arnold tx texture conversion wasn't respecting colorspace set by the artist +- _(pype)_ problems with avalon db sync +- _(maya)_ ftrack was rounding FPS making it inconsistent +- _(pype)_ wrong icon names in Creator +- _(maya)_ scene inventory wasn't showing anything if representation was removed from database after it's been loaded to the scene +- _(nukestudio)_ multiple bugs squashed +- _(loader)_ loader was taking long time to show all the loading action when first launcher in maya + +## 2.2.0 ## +_release date: 8 Sept 2019_ + +**new**: +- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts +- _(nuke)_ option to choose deadline chunk size on write nodes +- _(nukestudio)_ added option to publish soft effects (subTrackItems) from NukeStudio as subsets including LUT files. these can then be loaded in nuke or NukeStudio +- _(nuke)_ option to build nuke script from previously published latest versions of plate and render subsets. +- _(nuke)_ nuke writes now have deadline tab. +- _(ftrack)_ Prepare Project action can now be used for creating the base folder structure on disk and in ftrack, setting up all the initial project attributes and it automatically prepares `pype_project_config` folder for the given project. +- _(clockify)_ Added support for time tracking in clockify. This currently in addition to ftrack time logs, but does not completely replace them. +- _(pype)_ any attributes in Creator and Loader plugins can now be customised using pype preset system + +**changed**: +- nukestudio now uses workio API for workfiles +- _(maya)_ "FIX FPS" prompt in maya now appears in the middle of the screen +- _(muster)_ can now be configured with custom templates +- _(pype)_ global publishing plugins can now be configured using presets as well as host specific ones + + +**fix**: +- wrong version retrieval from path in certain scenarios +- nuke reset resolution wasn't working in certain scenarios + +## 2.1.0 ## +_release date: 6 Aug 2019_ + +A large cleanup release. Most of the change are under the hood. + +**new**: +- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts +- _(pype)_ Added configurable option to add burnins to any generated quicktimes +- _(ftrack)_ Action that identifies what machines pype is running on. +- _(system)_ unify subprocess calls +- _(maya)_ add audio to review quicktimes +- _(nuke)_ add crop before write node to prevent overscan problems in ffmpeg +- **Nuke Studio** publishing and workfiles support +- **Muster** render manager support +- _(nuke)_ Framerange, FPS and Resolution are set automatically at startup +- _(maya)_ Ability to load published sequences as image planes +- _(system)_ Ftrack event that sets asset folder permissions based on task assignees in ftrack. +- _(maya)_ Pyblish plugin that allow validation of maya attributes +- _(system)_ added better startup logging to tray debug, including basic connection information +- _(avalon)_ option to group published subsets to groups in the loader +- _(avalon)_ loader family filters are working now + +**changed**: +- change multiple key attributes to unify their behaviour across the pipeline + - `frameRate` to `fps` + - `startFrame` to `frameStart` + - `endFrame` to `frameEnd` + - `fstart` to `frameStart` + - `fend` to `frameEnd` + - `handle_start` to `handleStart` + - `handle_end` to `handleEnd` + - `resolution_width` to `resolutionWidth` + - `resolution_height` to `resolutionHeight` + - `pixel_aspect` to `pixelAspect` + +- _(nuke)_ write nodes are now created inside group with only some attributes editable by the artist +- rendered frames are now deleted from temporary location after their publishing is finished. +- _(ftrack)_ RV action can now be launched from any entity +- after publishing only refresh button is now available in pyblish UI +- added context instance pyblish-lite so that artist knows if context plugin fails +- _(avalon)_ allow opening selected files using enter key +- _(avalon)_ core updated to v5.2.9 with our forked changes on top + +**fix**: +- faster hierarchy retrieval from db +- _(nuke)_ A lot of stability enhancements +- _(nuke studio)_ A lot of stability enhancements +- _(nuke)_ now only renders a single write node on farm +- _(ftrack)_ pype would crash when launcher project level task +- work directory was sometimes not being created correctly +- major pype.lib cleanup. Removing of unused functions, merging those that were doing the same and general house cleaning. +- _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner diff --git a/changelog.md b/changelog.md deleted file mode 100644 index bdee041615..0000000000 --- a/changelog.md +++ /dev/null @@ -1,120 +0,0 @@ -# Pype changelog # -Welcome to pype changelog - -## 2.3.0 ## -_release date: 6 Oct 2019_ - -**new**: -- _(maya)_ support for yeti rigs and yeti caches -- _(maya)_ validator for comparing arbitrary attributes against ftrack -- _(pype)_ burnins can now show current date and time -- _(muster)_ pools can now be set in render globals in maya -- _(pype)_ Rest API has been implemented in beta stage -- _(nuke)_ LUT loader has been added -- _(pype)_ rudimentary user module has been added as preparation for user management -- _(pype)_ a simple logging GUI has been added to pype tray -- _(nuke)_ nuke can now bake input process into mov -- _(maya)_ imported models now have selection handle displayed by defaulting -- _(avalon)_ it's is now possible to load multiple assets at once using loader -- _(maya)_ added ability to automatically connect yeti rig to a mesh upon loading - -**changed**: -- _(ftrack)_ event server now runs two parallel processes and is able to keep queue of events to process. -- _(nuke)_ task name is now added to all rendered subsets -- _(pype)_ adding more families to standalone publisher -- _(pype)_ standalone publisher now uses pyblish-lite -- _(pype)_ standalone publisher can now create review quicktimes -- _(ftrack)_ queries to ftrack were sped up -- _(ftrack)_ multiple ftrack action have been deprecated -- _(avalon)_ avalon upstream has been updated to 5.5.0 -- _(nukestudio)_ published transforms can now be animated -- - -**fix**: -- _(maya)_ fps popup button didn't work in some cases -- _(maya)_ geometry instances and references in maya were losing shader assignments -- _(muster)_ muster rendering templates were not working correctly -- _(maya)_ arnold tx texture conversion wasn't respecting colorspace set by the artist -- _(pype)_ problems with avalon db sync -- _(maya)_ ftrack was rounding FPS making it inconsistent -- _(pype)_ wrong icon names in Creator -- _(maya)_ scene inventory wasn't showing anything if representation was removed from database after it's been loaded to the scene -- _(nukestudio)_ multiple bugs squashed -- _(loader)_ loader was taking long time to show all the loading action when first launcher in maya - -## 2.2.0 ## -_release date: 8 Sept 2019_ - -**new**: -- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts -- _(nuke)_ option to choose deadline chunk size on write nodes -- _(nukestudio)_ added option to publish soft effects (subTrackItems) from NukeStudio as subsets including LUT files. these can then be loaded in nuke or NukeStudio -- _(nuke)_ option to build nuke script from previously published latest versions of plate and render subsets. -- _(nuke)_ nuke writes now have deadline tab. -- _(ftrack)_ Prepare Project action can now be used for creating the base folder structure on disk and in ftrack, setting up all the initial project attributes and it automatically prepares `pype_project_config` folder for the given project. -- _(clockify)_ Added support for time tracking in clockify. This currently in addition to ftrack time logs, but does not completely replace them. -- _(pype)_ any attributes in Creator and Loader plugins can now be customised using pype preset system - -**changed**: -- nukestudio now uses workio API for workfiles -- _(maya)_ "FIX FPS" prompt in maya now appears in the middle of the screen -- _(muster)_ can now be configured with custom templates -- _(pype)_ global publishing plugins can now be configured using presets as well as host specific ones - - -**fix**: -- wrong version retrieval from path in certain scenarios -- nuke reset resolution wasn't working in certain scenarios - -## 2.1.0 ## -_release date: 6 Aug 2019_ - -A large cleanup release. Most of the change are under the hood. - -**new**: -- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts -- _(pype)_ Added configurable option to add burnins to any generated quicktimes -- _(ftrack)_ Action that identifies what machines pype is running on. -- _(system)_ unify subprocess calls -- _(maya)_ add audio to review quicktimes -- _(nuke)_ add crop before write node to prevent overscan problems in ffmpeg -- **Nuke Studio** publishing and workfiles support -- **Muster** render manager support -- _(nuke)_ Framerange, FPS and Resolution are set automatically at startup -- _(maya)_ Ability to load published sequences as image planes -- _(system)_ Ftrack event that sets asset folder permissions based on task assignees in ftrack. -- _(maya)_ Pyblish plugin that allow validation of maya attributes -- _(system)_ added better startup logging to tray debug, including basic connection information -- _(avalon)_ option to group published subsets to groups in the loader -- _(avalon)_ loader family filters are working now - -**changed**: -- change multiple key attributes to unify their behaviour across the pipeline - - `frameRate` to `fps` - - `startFrame` to `frameStart` - - `endFrame` to `frameEnd` - - `fstart` to `frameStart` - - `fend` to `frameEnd` - - `handle_start` to `handleStart` - - `handle_end` to `handleEnd` - - `resolution_width` to `resolutionWidth` - - `resolution_height` to `resolutionHeight` - - `pixel_aspect` to `pixelAspect` - -- _(nuke)_ write nodes are now created inside group with only some attributes editable by the artist -- rendered frames are now deleted from temporary location after their publishing is finished. -- _(ftrack)_ RV action can now be launched from any entity -- after publishing only refresh button is now available in pyblish UI -- added context instance pyblish-lite so that artist knows if context plugin fails -- _(avalon)_ allow opening selected files using enter key -- _(avalon)_ core updated to v5.2.9 with our forked changes on top - -**fix**: -- faster hierarchy retrieval from db -- _(nuke)_ A lot of stability enhancements -- _(nuke studio)_ A lot of stability enhancements -- _(nuke)_ now only renders a single write node on farm -- _(ftrack)_ pype would crash when launcher project level task -- work directory was sometimes not being created correctly -- major pype.lib cleanup. Removing of unused functions, merging those that were doing the same and general house cleaning. -- _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner From 93f7391e5a899a14adb1c8a0c964a26a9e565c23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 17:49:19 +0200 Subject: [PATCH 819/947] convert data from gui data t o config data --- pype/tools/config_setting/config_setting/widgets/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 172450684b..02116b6044 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -126,7 +126,7 @@ class SystemWidget(QtWidgets.QWidget): if value is not lib.NOT_SET: _data.update(value) - values = _data["system"] + values = lib.convert_gui_data_to_overrides(_data.get("system", {})) dirpath = os.path.dirname(SYSTEM_CONFIGURATIONS_PATH) if not os.path.exists(dirpath): @@ -456,7 +456,7 @@ class ProjectWidget(QtWidgets.QWidget): if value is not lib.NOT_SET: _data.update(value) - output = _data["project"] + output = lib.convert_gui_data_to_overrides(_data.get("project", {})) dirpath = os.path.dirname(PROJECT_CONFIGURATIONS_PATH) if not os.path.exists(dirpath): From 037a573f7ca7f774d3855bbf16250844dd517827 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 17:51:22 +0200 Subject: [PATCH 820/947] a lot of changes to be able to get studio overrides --- .../config_setting/widgets/inputs.py | 223 ++++++++---------- 1 file changed, 93 insertions(+), 130 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3a3d517cc1..cf42d47ab5 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -16,15 +16,20 @@ class ConfigObject(AbstractConfigObject): default_state = "" - _has_studio_override = False _as_widget = False - _is_overriden = False - _is_modified = False - _was_overriden = False - _is_invalid = False _is_group = False + # TODO not implemented yet _is_nullable = False + _has_studio_override = False + _had_studio_override = False + + _is_overriden = False + _was_overriden = False + + _is_modified = False + _is_invalid = False + _any_parent_is_group = None _log = None @@ -38,6 +43,10 @@ class ConfigObject(AbstractConfigObject): def has_studio_override(self): return self._has_studio_override or self._parent.has_studio_override + @property + def had_studio_override(self): + return self._had_studio_override + @property def any_parent_is_group(self): if self._any_parent_is_group is None: @@ -47,7 +56,11 @@ class ConfigObject(AbstractConfigObject): @property def is_modified(self): """Has object any changes that require saving.""" - return self._is_modified or (self.was_overriden != self.is_overriden) + return ( + self._is_modified + or (self.was_overriden != self.is_overriden) + or (self.has_studio_override != self.had_studio_override) + ) @property def is_overriden(self): @@ -200,8 +213,9 @@ class InputObject(ConfigObject): ) self.default_value = value - if not self.has_studio_override: - self.set_value(value) + self._has_studio_override = False + self._had_studio_override = False + self.set_value(value) def update_studio_values(self, parent_values): value = NOT_SET @@ -219,8 +233,31 @@ class InputObject(ConfigObject): self.set_value(self.default_value) self._has_studio_override = False + self._had_studio_override = bool(self._has_studio_override) self._is_modified = False + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.update_style() + + self.value_changed.emit(self) + def studio_overrides(self): if not self.has_studio_override: return NOT_SET, False @@ -372,24 +409,6 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): # value as `value` the `_on_value_change` is not triggered self.checkbox.setChecked(value) - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self._is_invalid: - self._is_modified = True - elif self._is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.studio_value - - self.update_style() - - self.value_changed.emit(self) - def update_style(self): state = self.style_state( self.has_studio_override, @@ -464,24 +483,6 @@ class NumberWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.input_field.setValue(value) - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self._is_invalid: - self._is_modified = True - elif self._is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.studio_value - - self.update_style() - - self.value_changed.emit(self) - def update_style(self): state = self.style_state( self.has_studio_override, @@ -559,24 +560,6 @@ class TextWidget(QtWidgets.QWidget, InputObject): else: self.text_input.setText(value) - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self._is_invalid: - self._is_modified = True - elif self._is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.studio_value - - self.update_style() - - self.value_changed.emit(self) - def update_style(self): state = self.style_state( self.has_studio_override, @@ -651,24 +634,6 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): self.path_input.clear_end_path() super(PathInput, self).focusOutEvent(event) - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self._is_invalid: - self._is_modified = True - elif self.is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.studio_value - - self.update_style() - - self.value_changed.emit(self) - def update_style(self): state = self.style_state( self.has_studio_override, @@ -804,24 +769,9 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def set_value(self, value): self.text_input.set_value(value) - def _on_value_change(self, item=None): + def _on_value_change(self, *args, **kwargs): self._is_invalid = self.text_input.has_invalid_value() - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self._is_invalid: - self._is_modified = True - elif self._is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.studio_value - - self.update_style() - - self.value_changed.emit(self) + return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) def update_style(self): state = self.style_state( @@ -1006,24 +956,6 @@ class ListWidget(QtWidgets.QWidget, InputObject): if self.count() == 0: self.add_row(is_empty=True) - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - if self.is_overidable: - self._is_overriden = True - - if self._is_invalid: - self._is_modified = True - elif self._is_overriden: - self._is_modified = self.item_value() != self.override_value - else: - self._is_modified = self.item_value() != self.studio_value - - self.update_style() - - self.value_changed.emit(self) - def add_row(self, row=None, value=None, is_empty=False): # Create new item item_widget = ListItem( @@ -1351,6 +1283,9 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.add_row(is_empty=True) def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + fields_by_keys = collections.defaultdict(list) for input_field in self.input_fields: key = input_field.key_value() @@ -1367,18 +1302,19 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): field.is_key_duplicated = True field.update_style() - if self.ignore_value_changes: - return - if self.is_overidable: self._is_overriden = True + else: + self._has_studio_override = True - if self.is_invalid: + if self._is_invalid: self._is_modified = True elif self._is_overriden: self._is_modified = self.item_value() != self.override_value - else: + elif self._has_studio_override: self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value self.update_style() @@ -1633,6 +1569,12 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): if parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) + if value is NOT_SET: + self._has_studio_override = False + else: + self._has_studio_override = True + self._had_studio_override = bool(self._has_studio_override) + for item in self.input_fields: item.update_studio_values(value) @@ -1669,6 +1611,8 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): if self.is_group: if self.is_overidable: self._is_overriden = True + else: + self._has_studio_override = True self.hierarchical_style_update() @@ -1702,7 +1646,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): self._child_state = child_state state = self.style_state( - child_has_studio_override, + self.had_studio_override, child_invalid, self.is_overriden, self.is_modified @@ -1724,7 +1668,10 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): @property def child_has_studio_override(self): for input_field in self.input_fields: - if input_field.child_has_studio_override: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): return True return False @@ -1770,7 +1717,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): values = {} groups = [] for input_field in self.input_fields: - value, is_group = input_field.overrides() + value, is_group = input_field.studio_overrides() if value is not NOT_SET: values.update(value) if is_group: @@ -1850,7 +1797,10 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): @property def child_has_studio_override(self): for input_field in self.input_fields: - if input_field.child_has_studio_override: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): return True return False @@ -1896,6 +1846,8 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): if self.is_group: if self.is_overidable: self._is_overriden = True + else: + self._has_studio_override = True self.hierarchical_style_update() self.value_changed.emit(self) @@ -1982,7 +1934,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): values = {} groups = [] for input_field in self.input_fields: - value, is_group = input_field.overrides() + value, is_group = input_field.studio_overrides() if value is not NOT_SET: values.update(value) if is_group: @@ -2174,20 +2126,24 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): if self.is_overidable: self._is_overriden = True + else: + self._has_studio_override = True if self._is_invalid: self._is_modified = True elif self._is_overriden: self._is_modified = self.item_value() != self.override_value - else: + elif self._has_studio_override: self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value self.hierarchical_style_update() self.value_changed.emit(self) def update_style(self, is_overriden=None): - child_has_studio_override = self.has_studio_override + child_has_studio_override = self.child_has_studio_override child_modified = self.child_modified child_invalid = self.child_invalid child_state = self.style_state( @@ -2240,7 +2196,10 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): @property def child_has_studio_override(self): for input_field in self.input_fields: - if input_field.child_has_studio_override: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): return True return False @@ -2407,6 +2366,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): def _on_value_change(self, item=None): if self.ignore_value_changes: return + self.value_changed.emit(self) if self.any_parent_is_group: self.hierarchical_style_update() @@ -2414,7 +2374,10 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): @property def child_has_studio_override(self): for input_field in self.input_fields: - if input_field.child_has_studio_override: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): return True return False @@ -2467,7 +2430,7 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): values = {} groups = [] for input_field in self.input_fields: - value, is_group = input_field.overrides() + value, is_group = input_field.studio_overrides() if value is not NOT_SET: values.update(value) if is_group: From 6bdee49e2fd0d66e2b32b31eb56bcac3699d26f6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 17:52:52 +0200 Subject: [PATCH 821/947] underscored attributes are kept --- .../config_setting/widgets/base.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 02116b6044..cfbed884d9 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -17,10 +17,10 @@ from avalon import io class SystemWidget(QtWidgets.QWidget): is_overidable = False - has_studio_override = False - is_overriden = False - is_group = False - any_parent_is_group = False + has_studio_override = _has_studio_override = False + is_overriden = _is_overriden = False + is_group = _is_group = False + any_parent_is_group = _any_parent_is_group = False def __init__(self, parent=None): super(SystemWidget, self).__init__(parent) @@ -286,10 +286,10 @@ class ProjectListWidget(QtWidgets.QWidget): class ProjectWidget(QtWidgets.QWidget): - has_studio_override = False - is_overriden = False - is_group = False - any_parent_is_group = False + has_studio_override = _has_studio_override = False + is_overriden = _is_overriden = False + is_group = _is_group = False + any_parent_is_group = _any_parent_is_group = False def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) From b3f6c8296510e37f5940f945210496c64f6689b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 18:00:41 +0200 Subject: [PATCH 822/947] ignore value changes during loading of studio overrides --- .../config_setting/widgets/base.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index cfbed884d9..a143e584dd 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -139,8 +139,11 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() def _update_values(self): - default_values = default_configuration() - default_values = {"system": default_values["system_configurations"]} + self.ignore_value_changes = True + + default_values = { + "system": default_configuration()["system_configurations"] + } for input_field in self.input_fields: input_field.update_default_values(default_values) @@ -148,8 +151,7 @@ class SystemWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.update_studio_values(system_values) - for input_field in self.input_fields: - input_field.hierarchical_style_update() + self.ignore_value_changes = False def add_children_gui(self, child_configuration): item_type = child_configuration["type"] @@ -469,8 +471,11 @@ class ProjectWidget(QtWidgets.QWidget): self._update_values() def _update_values(self): - default_values = default_configuration() - default_values = {"project": default_values["project_configurations"]} + self.ignore_value_changes = True + + default_values = { + "project": default_configuration()["project_configurations"] + } for input_field in self.input_fields: input_field.update_default_values(default_values) @@ -478,5 +483,4 @@ class ProjectWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.update_studio_values(studio_values) - for input_field in self.input_fields: - input_field.hierarchical_style_update() + self.ignore_value_changes = False From a4babc31da11cab5e9841c5187e03d409db99640 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 18:00:59 +0200 Subject: [PATCH 823/947] few minor changes --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index cf42d47ab5..ff4448992d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -762,10 +762,10 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.text_input.textChanged.connect(self._on_value_change) def update_studio_values(self, parent_values): - super(RawJsonWidget, self).update_studio_values(parent_values) - self._is_invalid = self.text_input.has_invalid_value() + super(RawJsonWidget, self).update_studio_values(parent_values) + def set_value(self, value): self.text_input.set_value(value) @@ -1765,6 +1765,8 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): self._any_parent_is_group = any_parent_is_group self._is_group = input_data.get("is_group", False) + if self._is_group: + raise TypeError("DictInvisible can't be marked as group input.") self.setAttribute(QtCore.Qt.WA_TranslucentBackground) From 0506d513954a29950a517a194d8203c7da4da84b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 9 Sep 2020 18:38:54 +0200 Subject: [PATCH 824/947] fix dict studio override --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ff4448992d..4a71f0380c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1569,10 +1569,10 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): if parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) - if value is NOT_SET: - self._has_studio_override = False - else: + self._has_studio_override = False + if self.is_group and value is not NOT_SET: self._has_studio_override = True + self._had_studio_override = bool(self._has_studio_override) for item in self.input_fields: From d372f9077eff211a34c8849a85ee422679da5554 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 10 Sep 2020 09:54:58 +0200 Subject: [PATCH 825/947] changelog update --- .github_changelog_generator | 2 +- CHANGELOG.md | 565 ++++++++++++++++++++++++++++++++++++ HISTORY.md | 3 - 3 files changed, 566 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github_changelog_generator b/.github_changelog_generator index 93ab39f4d0..cd09ebcbfa 100644 --- a/.github_changelog_generator +++ b/.github_changelog_generator @@ -4,4 +4,4 @@ author=False unreleased=False since-tag=2.11.0 release-branch=master -enhancement-label=New / Enhancements +enhancement-label=**Enhancements:** diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..ba86b85eec --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,565 @@ +# Changelog + +## [2.12.0](https://github.com/pypeclub/pype/tree/2.12.0) (2020-09-09) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.8...2.12.0) + +**Enhancements:** + +- Less mongo connections [\#509](https://github.com/pypeclub/pype/pull/509) +- Nuke: adding image loader [\#499](https://github.com/pypeclub/pype/pull/499) +- Move launcher window to top if launcher action is clicked [\#450](https://github.com/pypeclub/pype/pull/450) +- Maya: better tile rendering support in Pype [\#446](https://github.com/pypeclub/pype/pull/446) +- Implementation of non QML launcher [\#443](https://github.com/pypeclub/pype/pull/443) +- Optional skip review on renders. [\#441](https://github.com/pypeclub/pype/pull/441) +- Ftrack: Option to push status from task to latest version [\#440](https://github.com/pypeclub/pype/pull/440) +- Properly containerize image plane loads. [\#434](https://github.com/pypeclub/pype/pull/434) +- Option to keep the review files. [\#426](https://github.com/pypeclub/pype/pull/426) +- Isolate view on instance members. [\#425](https://github.com/pypeclub/pype/pull/425) +- ftrack group is bcw compatible [\#418](https://github.com/pypeclub/pype/pull/418) +- Maya: Publishing of tile renderings on Deadline [\#398](https://github.com/pypeclub/pype/pull/398) +- Feature/little bit better logging gui [\#383](https://github.com/pypeclub/pype/pull/383) + +**Fixed bugs:** + +- Maya: Fix tile order for Draft Tile Assembler [\#511](https://github.com/pypeclub/pype/pull/511) +- Remove extra dash [\#501](https://github.com/pypeclub/pype/pull/501) +- Fix: strip dot from repre names in single frame renders [\#498](https://github.com/pypeclub/pype/pull/498) +- Better handling of destination during integrating [\#485](https://github.com/pypeclub/pype/pull/485) +- Fix: allow thumbnail creation for single frame renders [\#460](https://github.com/pypeclub/pype/pull/460) +- added missing argument to launch\_application in ftrack app handler [\#453](https://github.com/pypeclub/pype/pull/453) +- Burnins: Copy bit rate of input video to match quality. [\#448](https://github.com/pypeclub/pype/pull/448) +- Standalone publisher is now independent from tray [\#442](https://github.com/pypeclub/pype/pull/442) +- Bugfix/empty enumerator attributes [\#436](https://github.com/pypeclub/pype/pull/436) +- Fixed wrong order of "other" category collapssing in publisher [\#435](https://github.com/pypeclub/pype/pull/435) +- Multiple reviews where being overwritten to one. [\#424](https://github.com/pypeclub/pype/pull/424) +- Cleanup plugin fail on instances without staging dir [\#420](https://github.com/pypeclub/pype/pull/420) +- deprecated -intra parameter in ffmpeg to new `-g` [\#417](https://github.com/pypeclub/pype/pull/417) +- Delivery action can now work with entered path [\#397](https://github.com/pypeclub/pype/pull/397) + +**Merged pull requests:** + +- Review on instance.data [\#473](https://github.com/pypeclub/pype/pull/473) + +## [2.11.8](https://github.com/pypeclub/pype/tree/2.11.8) (2020-08-27) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.7...2.11.8) + +**Enhancements:** + +- DWAA support for Maya [\#382](https://github.com/pypeclub/pype/issues/382) +- Isolate View on Playblast [\#367](https://github.com/pypeclub/pype/issues/367) +- Maya: Tile rendering [\#297](https://github.com/pypeclub/pype/issues/297) +- single pype instance running [\#47](https://github.com/pypeclub/pype/issues/47) +- PYPE-649: projects don't guarantee backwards compatible environment [\#8](https://github.com/pypeclub/pype/issues/8) +- PYPE-663: separate venv for each deployed version [\#7](https://github.com/pypeclub/pype/issues/7) + +**Fixed bugs:** + +- pyblish pype - other group is collapsed before plugins are done [\#431](https://github.com/pypeclub/pype/issues/431) +- Alpha white edges in harmony on PNGs [\#412](https://github.com/pypeclub/pype/issues/412) +- harmony image loader picks wrong representations [\#404](https://github.com/pypeclub/pype/issues/404) +- Clockify crash when response contain symbol not allowed by UTF-8 [\#81](https://github.com/pypeclub/pype/issues/81) + +## [2.11.7](https://github.com/pypeclub/pype/tree/2.11.7) (2020-08-21) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.6...2.11.7) + +**Fixed bugs:** + +- Clean Up Baked Movie [\#369](https://github.com/pypeclub/pype/issues/369) +- celaction last workfile [\#459](https://github.com/pypeclub/pype/pull/459) + +## [2.11.6](https://github.com/pypeclub/pype/tree/2.11.6) (2020-08-18) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.5...2.11.6) + +**Enhancements:** + +- publisher app [\#56](https://github.com/pypeclub/pype/issues/56) + +## [2.11.5](https://github.com/pypeclub/pype/tree/2.11.5) (2020-08-13) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.4...2.11.5) + +**Enhancements:** + +- Switch from master to equivalent [\#220](https://github.com/pypeclub/pype/issues/220) +- Standalone publisher now only groups sequence if the extension is known [\#439](https://github.com/pypeclub/pype/pull/439) + +**Fixed bugs:** + +- Logs have been disable for editorial by default to speed up publishing [\#433](https://github.com/pypeclub/pype/pull/433) +- additional fixes for celaction [\#430](https://github.com/pypeclub/pype/pull/430) +- Harmony: invalid variable scope in validate scene settings [\#428](https://github.com/pypeclub/pype/pull/428) +- new representation name for audio was not accepted [\#427](https://github.com/pypeclub/pype/pull/427) + +## [2.11.4](https://github.com/pypeclub/pype/tree/2.11.4) (2020-08-10) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.3...2.11.4) + +**Enhancements:** + +- WebSocket server [\#135](https://github.com/pypeclub/pype/issues/135) +- standalonepublisher: editorial family features expansion \[master branch\] [\#411](https://github.com/pypeclub/pype/pull/411) + +## [2.11.3](https://github.com/pypeclub/pype/tree/2.11.3) (2020-08-04) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.2...2.11.3) + +**Fixed bugs:** + +- Harmony: publishing performance issues [\#408](https://github.com/pypeclub/pype/pull/408) + +## [2.11.2](https://github.com/pypeclub/pype/tree/2.11.2) (2020-07-31) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.1...2.11.2) + +**Fixed bugs:** + +- Ftrack to Avalon bug [\#406](https://github.com/pypeclub/pype/issues/406) + +## [2.11.1](https://github.com/pypeclub/pype/tree/2.11.1) (2020-07-29) + +[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.0...2.11.1) + +**Merged pull requests:** + +- Celaction: metadata json folder fixes on path [\#393](https://github.com/pypeclub/pype/pull/393) +- CelAction - version up method taken fro pype.lib [\#391](https://github.com/pypeclub/pype/pull/391) + + +## 2.11.0 ## + +_**release date:** 27 July 2020_ + +**new:** +- _(blender)_ namespace support [\#341](https://github.com/pypeclub/pype/pull/341) +- _(blender)_ start end frames [\#330](https://github.com/pypeclub/pype/pull/330) +- _(blender)_ camera asset [\#322](https://github.com/pypeclub/pype/pull/322) +- _(pype)_ toggle instances per family in pyblish GUI [\#320](https://github.com/pypeclub/pype/pull/320) +- _(pype)_ current release version is now shown in the tray menu [#379](https://github.com/pypeclub/pype/pull/379) + + +**improved:** +- _(resolve)_ tagging for publish [\#239](https://github.com/pypeclub/pype/issues/239) +- _(pype)_ Support publishing a subset of shots with standalone editorial [\#336](https://github.com/pypeclub/pype/pull/336) +- _(harmony)_ Basic support for palettes [\#324](https://github.com/pypeclub/pype/pull/324) +- _(photoshop)_ Flag outdated containers on startup and publish. [\#309](https://github.com/pypeclub/pype/pull/309) +- _(harmony)_ Flag Outdated containers [\#302](https://github.com/pypeclub/pype/pull/302) +- _(photoshop)_ Publish review [\#298](https://github.com/pypeclub/pype/pull/298) +- _(pype)_ Optional Last workfile launch [\#365](https://github.com/pypeclub/pype/pull/365) + + +**fixed:** +- _(premiere)_ workflow fixes [\#346](https://github.com/pypeclub/pype/pull/346) +- _(pype)_ pype-setup does not work with space in path [\#327](https://github.com/pypeclub/pype/issues/327) +- _(ftrack)_ Ftrack delete action cause circular error [\#206](https://github.com/pypeclub/pype/issues/206) +- _(nuke)_ Priority was forced to 50 [\#345](https://github.com/pypeclub/pype/pull/345) +- _(nuke)_ Fix ValidateNukeWriteKnobs [\#340](https://github.com/pypeclub/pype/pull/340) +- _(maya)_ If camera attributes are connected, we can ignore them. [\#339](https://github.com/pypeclub/pype/pull/339) +- _(pype)_ stop appending of tools environment to existing env [\#337](https://github.com/pypeclub/pype/pull/337) +- _(ftrack)_ Ftrack timeout needs to look at AVALON\_TIMEOUT [\#325](https://github.com/pypeclub/pype/pull/325) +- _(harmony)_ Only zip files are supported. [\#310](https://github.com/pypeclub/pype/pull/310) +- _(pype)_ hotfix/Fix event server mongo uri [\#305](https://github.com/pypeclub/pype/pull/305) +- _(photoshop)_ Subset was not named or validated correctly. [\#304](https://github.com/pypeclub/pype/pull/304) + + + + +## 2.10.0 ## + +_**release date:** 17 June 2020_ + +**new:** +- _(harmony)_ **Toon Boom Harmony** has been greatly extended to support rigging, scene build, animation and rendering workflows. [#270](https://github.com/pypeclub/pype/issues/270) [#271](https://github.com/pypeclub/pype/issues/271) [#190](https://github.com/pypeclub/pype/issues/190) [#191](https://github.com/pypeclub/pype/issues/191) [#172](https://github.com/pypeclub/pype/issues/172) [#168](https://github.com/pypeclub/pype/issues/168) +- _(pype)_ Added support for rudimentary **edl publishing** into individual shots. [#265](https://github.com/pypeclub/pype/issues/265) +- _(celaction)_ Simple **Celaction** integration has been added with support for workfiles and rendering. [#255](https://github.com/pypeclub/pype/issues/255) +- _(maya)_ Support for multiple job types when submitting to the farm. We can now render Maya or Standalone render jobs for Vray and Arnold (limited support for arnold) [#204](https://github.com/pypeclub/pype/issues/204) +- _(photoshop)_ Added initial support for Photoshop [#232](https://github.com/pypeclub/pype/issues/232) + +**improved:** +- _(blender)_ Updated support for rigs and added support Layout family [#233](https://github.com/pypeclub/pype/issues/233) [#226](https://github.com/pypeclub/pype/issues/226) +- _(premiere)_ It is now possible to choose different storage root for workfiles of different task types. [#255](https://github.com/pypeclub/pype/issues/255) +- _(maya)_ Support for unmerged AOVs in Redshift multipart EXRs [#197](https://github.com/pypeclub/pype/issues/197) +- _(pype)_ Pype repository has been refactored in preparation for 3.0 release [#169](https://github.com/pypeclub/pype/issues/169) +- _(deadline)_ All file dependencies are now passed to deadline from maya to prevent premature start of rendering if caches or textures haven't been coppied over yet. [#195](https://github.com/pypeclub/pype/issues/195) +- _(nuke)_ Script validation can now be made optional. [#194](https://github.com/pypeclub/pype/issues/194) +- _(pype)_ Publishing can now be stopped at any time. [#194](https://github.com/pypeclub/pype/issues/194) + +**fix:** +- _(pype)_ Pyblish-lite has been integrated into pype repository, plus various publishing GUI fixes. [#274](https://github.com/pypeclub/pype/issues/274) [#275](https://github.com/pypeclub/pype/issues/275) [#268](https://github.com/pypeclub/pype/issues/268) [#227](https://github.com/pypeclub/pype/issues/227) [#238](https://github.com/pypeclub/pype/issues/238) +- _(maya)_ Alembic extractor was getting wrong frame range type in certain scenarios [#254](https://github.com/pypeclub/pype/issues/254) +- _(maya)_ Attaching a render to subset in maya was not passing validation in certain scenarios [#256](https://github.com/pypeclub/pype/issues/256) +- _(ftrack)_ Various small fixes to ftrack sync [#263](https://github.com/pypeclub/pype/issues/263) [#259](https://github.com/pypeclub/pype/issues/259) +- _(maya)_ Look extraction is now able to skp invalid connections in shaders [#207](https://github.com/pypeclub/pype/issues/207) + + + + +## 2.9.0 ## + +_**release date:** 25 May 2020_ + +**new:** +- _(pype)_ Support for **Multiroot projects**. You can now store project data on multiple physical or virtual storages and target individual publishes to these locations. For instance render can be stored on a faster storage than the rest of the project. [#145](https://github.com/pypeclub/pype/issues/145), [#38](https://github.com/pypeclub/pype/issues/38) +- _(harmony)_ Basic implementation of **Toon Boom Harmony** has been added. [#142](https://github.com/pypeclub/pype/issues/142) +- _(pype)_ OSX support is in public beta now. There are issues to be expected, but the main implementation should be functional. [#141](https://github.com/pypeclub/pype/issues/141) + + +**improved:** + +- _(pype)_ **Review extractor** has been completely rebuilt. It now supports granular filtering so you can create **multiple outputs** for different tasks, families or hosts. [#103](https://github.com/pypeclub/pype/issues/103), [#166](https://github.com/pypeclub/pype/issues/166), [#165](https://github.com/pypeclub/pype/issues/165) +- _(pype)_ **Burnin** generation had been extended to **support same multi-output filtering** as review extractor [#103](https://github.com/pypeclub/pype/issues/103) +- _(pype)_ Publishing file templates can now be specified in config for each individual family [#114](https://github.com/pypeclub/pype/issues/114) +- _(pype)_ Studio specific plugins can now be appended to pype standard publishing plugins. [#112](https://github.com/pypeclub/pype/issues/112) +- _(nukestudio)_ Reviewable clips no longer need to be previously cut, exported and re-imported to timeline. **Pype can now dynamically cut reviewable quicktimes** from continuous offline footage during publishing. [#23](https://github.com/pypeclub/pype/issues/23) +- _(deadline)_ Deadline can now correctly differentiate between staging and production pype. [#154](https://github.com/pypeclub/pype/issues/154) +- _(deadline)_ `PYPE_PYTHON_EXE` env variable can now be used to direct publishing to explicit python installation. [#120](https://github.com/pypeclub/pype/issues/120) +- _(nuke)_ Nuke now check for new version of loaded data on file open. [#140](https://github.com/pypeclub/pype/issues/140) +- _(nuke)_ frame range and limit checkboxes are now exposed on write node. [#119](https://github.com/pypeclub/pype/issues/119) + + + +**fix:** + +- _(nukestudio)_ Project Location was using backslashes which was breaking nukestudio native exporting in certains configurations [#82](https://github.com/pypeclub/pype/issues/82) +- _(nukestudio)_ Duplicity in hierarchy tags was prone to throwing publishing error [#130](https://github.com/pypeclub/pype/issues/130), [#144](https://github.com/pypeclub/pype/issues/144) +- _(ftrack)_ multiple stability improvements [#157](https://github.com/pypeclub/pype/issues/157), [#159](https://github.com/pypeclub/pype/issues/159), [#128](https://github.com/pypeclub/pype/issues/128), [#118](https://github.com/pypeclub/pype/issues/118), [#127](https://github.com/pypeclub/pype/issues/127) +- _(deadline)_ multipart EXRs were stopping review publishing on the farm. They are still not supported for automatic review generation, but the publish will go through correctly without the quicktime. [#155](https://github.com/pypeclub/pype/issues/155) +- _(deadline)_ If deadline is non-responsive it will no longer freeze host when publishing [#149](https://github.com/pypeclub/pype/issues/149) +- _(deadline)_ Sometimes deadline was trying to launch render before all the source data was coppied over. [#137](https://github.com/pypeclub/pype/issues/137) _(harmony)_ Basic implementation of **Toon Boom Harmony** has been added. [#142](https://github.com/pypeclub/pype/issues/142) +- _(nuke)_ Filepath knob wasn't updated properly. [#131](https://github.com/pypeclub/pype/issues/131) +- _(maya)_ When extracting animation, the "Write Color Set" options on the instance were not respected. [#108](https://github.com/pypeclub/pype/issues/108) +- _(maya)_ Attribute overrides for AOV only worked for the legacy render layers. Now it works for new render setup as well [#132](https://github.com/pypeclub/pype/issues/132) +- _(maya)_ Stability and usability improvements in yeti workflow [#104](https://github.com/pypeclub/pype/issues/104) + + + + +## 2.8.0 ## + +_**release date:** 20 April 2020_ + +**new:** + +- _(pype)_ Option to generate slates from json templates. [PYPE-628] [#26](https://github.com/pypeclub/pype/issues/26) +- _(pype)_ It is now possible to automate loading of published subsets into any scene. Documentation will follow :). [PYPE-611] [#24](https://github.com/pypeclub/pype/issues/24) + +**fix:** + +- _(maya)_ Some Redshift render tokens could break publishing. [PYPE-778] [#33](https://github.com/pypeclub/pype/issues/33) +- _(maya)_ Publish was not preserving maya file extension. [#39](https://github.com/pypeclub/pype/issues/39) +- _(maya)_ Rig output validator was failing on nodes without shapes. [#40](https://github.com/pypeclub/pype/issues/40) +- _(maya)_ Yeti caches can now be properly versioned up in the scene inventory. [#40](https://github.com/pypeclub/pype/issues/40) +- _(nuke)_ Build first workfiles was not accepting jpeg sequences. [#34](https://github.com/pypeclub/pype/issues/34) +- _(deadline)_ Trying to generate ffmpeg review from multipart EXRs no longer crashes publishing. [PYPE-781] +- _(deadline)_ Render publishing is more stable in multiplatform environments. [PYPE-775] + + + + +## 2.7.0 ## + +_**release date:** 30 March 2020_ + +**new:** + +- _(maya)_ Artist can now choose to load multiple references of the same subset at once [PYPE-646, PYPS-81] +- _(nuke)_ Option to use named OCIO colorspaces for review colour baking. [PYPS-82] +- _(pype)_ Pype can now work with `master` versions for publishing and loading. These are non-versioned publishes that are overwritten with the latest version during publish. These are now supported in all the GUIs, but their publishing is deactivated by default. [PYPE-653] +- _(blender)_ Added support for basic blender workflow. We currently support `rig`, `model` and `animation` families. [PYPE-768] +- _(pype)_ Source timecode can now be used in burn-ins. [PYPE-777] +- _(pype)_ Review outputs profiles can now specify delivery resolution different than project setting [PYPE-759] +- _(nuke)_ Bookmark to current context is now added automatically to all nuke browser windows. [PYPE-712] + +**change:** + +- _(maya)_ It is now possible to publish camera without. baking. Keep in mind that unbaked cameras can't be guaranteed to work in other hosts. [PYPE-595] +- _(maya)_ All the renders from maya are now grouped in the loader by their Layer name. [PYPE-482] +- _(nuke/hiero)_ Any publishes from nuke and hiero can now be versioned independently of the workfile. [PYPE-728] + + +**fix:** + +- _(nuke)_ Mixed slashes caused issues in ocio config path. +- _(pype)_ Intent field in pyblish GUI was passing label instead of value to ftrack. [PYPE-733] +- _(nuke)_ Publishing of pre-renders was inconsistent. [PYPE-766] +- _(maya)_ Handles and frame ranges were inconsistent in various places during publishing. +- _(nuke)_ Nuke was crashing if it ran into certain missing knobs. For example DPX output missing `autocrop` [PYPE-774] +- _(deadline)_ Project overrides were not working properly with farm render publishing. +- _(hiero)_ Problems with single frame plates publishing. +- _(maya)_ Redshift RenderPass token were breaking render publishing. [PYPE-778] +- _(nuke)_ Build first workfile was not accepting jpeg sequences. +- _(maya)_ Multipart (Multilayer) EXRs were breaking review publishing due to FFMPEG incompatiblity [PYPE-781] + + + +## 2.6.0 ## + +_**release date:** 9 March 2020_ + +**change:** +- _(maya)_ render publishing has been simplified and made more robust. Render setup layers are now automatically added to publishing subsets and `render globals` family has been replaced with simple `render` [PYPE-570] +- _(avalon)_ change context and workfiles apps, have been merged into one, that allows both actions to be performed at the same time. [PYPE-747] +- _(pype)_ thumbnails are now automatically propagate to asset from the last published subset in the loader +- _(ftrack)_ publishing comment and intent are now being published to ftrack note as well as describtion. [PYPE-727] +- _(pype)_ when overriding existing version new old representations are now overriden, instead of the new ones just being appended. (to allow this behaviour, the version validator need to be disabled. [PYPE-690]) +- _(pype)_ burnin preset has been significantly simplified. It now doesn't require passing function to each field, but only need the actual text template. to use this, all the current burnin PRESETS MUST BE UPDATED for all the projects. +- _(ftrack)_ credentials are now stored on a per server basis, so it's possible to switch between ftrack servers without having to log in and out. [PYPE-723] + + +**new:** +- _(pype)_ production and development deployments now have different colour of the tray icon. Orange for Dev and Green for production [PYPE-718] +- _(maya)_ renders can now be attached to a publishable subset rather than creating their own subset. For example it is possible to create a reviewable `look` or `model` render and have it correctly attached as a representation of the subsets [PYPE-451] +- _(maya)_ after saving current scene into a new context (as a new shot for instance), all the scene publishing subsets data gets re-generated automatically to match the new context [PYPE-532] +- _(pype)_ we now support project specific publish, load and create plugins [PYPE-740] +- _(ftrack)_ new action that allow archiving/deleting old published versions. User can keep how many of the latest version to keep when the action is ran. [PYPE-748, PYPE-715] +- _(ftrack)_ it is now possible to monitor and restart ftrack event server using ftrack action. [PYPE-658] +- _(pype)_ validator that prevent accidental overwrites of previously published versions. [PYPE-680] +- _(avalon)_ avalon core updated to version 5.6.0 +- _(maya)_ added validator to make sure that relative paths are used when publishing arnold standins. +- _(nukestudio)_ it is now possible to extract and publish audio family from clip in nuke studio [PYPE-682] + +**fix**: +- _(maya)_ maya set framerange button was ignoring handles [PYPE-719] +- _(ftrack)_ sync to avalon was sometime crashing when ran on empty project +- _(nukestudio)_ publishing same shots after they've been previously archived/deleted would result in a crash. [PYPE-737] +- _(nuke)_ slate workflow was breaking in certain scenarios. [PYPE-730] +- _(pype)_ rendering publish workflow has been significantly improved to prevent error resulting from implicit render collection. [PYPE-665, PYPE-746] +- _(pype)_ launching application on a non-synced project resulted in obscure [PYPE-528] +- _(pype)_ missing keys in burnins no longer result in an error. [PYPE-706] +- _(ftrack)_ create folder structure action was sometimes failing for project managers due to wrong permissions. +- _(Nukestudio)_ using `source` in the start frame tag could result in wrong frame range calculation +- _(ftrack)_ sync to avalon action and event have been improved by catching more edge cases and provessing them properly. + + + +## 2.5.0 ## + +_**release date:** 11 Feb 2020_ + +**change:** +- _(pype)_ added many logs for easier debugging +- _(pype)_ review presets can now be separated between 2d and 3d renders [PYPE-693] +- _(pype)_ anatomy module has been greatly improved to allow for more dynamic pulblishing and faster debugging [PYPE-685] +- _(pype)_ avalon schemas have been moved from `pype-config` to `pype` repository, for simplification. [PYPE-670] +- _(ftrack)_ updated to latest ftrack API +- _(ftrack)_ publishing comments now appear in ftrack also as a note on version with customisable category [PYPE-645] +- _(ftrack)_ delete asset/subset action had been improved. It is now able to remove multiple entities and descendants of the selected entities [PYPE-361, PYPS-72] +- _(workfiles)_ added date field to workfiles app [PYPE-603] +- _(maya)_ old deprecated loader have been removed in favour of a single unified reference loader (old scenes will upgrade automatically to the new loader upon opening) [PYPE-633, PYPE-697] +- _(avalon)_ core updated to 5.5.15 [PYPE-671] +- _(nuke)_ library loader is now available in nuke [PYPE-698] + + +**new:** +- _(pype)_ added pype render wrapper to allow rendering on mixed platform farms. [PYPE-634] +- _(pype)_ added `pype launch` command. It let's admin run applications with dynamically built environment based on the given context. [PYPE-634] +- _(pype)_ added support for extracting review sequences with burnins [PYPE-657] +- _(publish)_ users can now set intent next to a comment when publishing. This will then be reflected on an attribute in ftrack. [PYPE-632] +- _(burnin)_ timecode can now be added to burnin +- _(burnin)_ datetime keys can now be added to burnin and anatomy [PYPE-651] +- _(burnin)_ anatomy templates can now be used in burnins. [PYPE=626] +- _(nuke)_ new validator for render resolution +- _(nuke)_ support for attach slate to nuke renders [PYPE-630] +- _(nuke)_ png sequences were added to loaders +- _(maya)_ added maya 2020 compatibility [PYPE-677] +- _(maya)_ ability to publish and load .ASS standin sequences [PYPS-54] +- _(pype)_ thumbnails can now be published and are visible in the loader. `AVALON_THUMBNAIL_ROOT` environment variable needs to be set for this to work [PYPE-573, PYPE-132] +- _(blender)_ base implementation of blender was added with publishing and loading of .blend files [PYPE-612] +- _(ftrack)_ new action for preparing deliveries [PYPE-639] + + +**fix**: +- _(burnin)_ more robust way of finding ffmpeg for burnins. +- _(pype)_ improved UNC paths remapping when sending to farm. +- _(pype)_ float frames sometimes made their way to representation context in database, breaking loaders [PYPE-668] +- _(pype)_ `pype install --force` was failing sometimes [PYPE-600] +- _(pype)_ padding in published files got calculated wrongly sometimes. It is now instead being always read from project anatomy. [PYPE-667] +- _(publish)_ comment publishing was failing in certain situations +- _(ftrack)_ multiple edge case scenario fixes in auto sync and sync-to-avalon action +- _(ftrack)_ sync to avalon now works on empty projects +- _(ftrack)_ thumbnail update event was failing when deleting entities [PYPE-561] +- _(nuke)_ loader applies proper colorspaces from Presets +- _(nuke)_ publishing handles didn't always work correctly [PYPE-686] +- _(maya)_ assembly publishing and loading wasn't working correctly + + + + + +## 2.4.0 ## + +_**release date:** 9 Dec 2019_ + +**change:** +- _(ftrack)_ version to status ftrack event can now be configured from Presets + - based on preset `presets/ftracc/ftrack_config.json["status_version_to_task"]` +- _(ftrack)_ sync to avalon event has been completely re-written. It now supports most of the project management situations on ftrack including moving, renaming and deleting entities, updating attributes and working with tasks. +- _(ftrack)_ sync to avalon action has been also re-writen. It is now much faster (up to 100 times depending on a project structure), has much better logging and reporting on encountered problems, and is able to handle much more complex situations. +- _(ftrack)_ sync to avalon trigger by checking `auto-sync` toggle on ftrack [PYPE-504] +- _(pype)_ various new features in the REST api +- _(pype)_ new visual identity used across pype +- _(pype)_ started moving all requirements to pip installation rather than vendorising them in pype repository. Due to a few yet unreleased packages, this means that pype can temporarily be only installed in the offline mode. + +**new:** +- _(nuke)_ support for publishing gizmos and loading them as viewer processes +- _(nuke)_ support for publishing nuke nodes from backdrops and loading them back +- _(pype)_ burnins can now work with start and end frames as keys + - use keys `{frame_start}`, `{frame_end}` and `{current_frame}` in burnin preset to use them. [PYPS-44,PYPS-73, PYPE-602] +- _(pype)_ option to filter logs by user and level in loggin GUI +- _(pype)_ image family added to standalone publisher [PYPE-574] +- _(pype)_ matchmove family added to standalone publisher [PYPE-574] +- _(nuke)_ validator for comparing arbitrary knobs with values from presets +- _(maya)_ option to force maya to copy textures in the new look publish rather than hardlinking them +- _(pype)_ comments from pyblish GUI are now being added to ftrack version +- _(maya)_ validator for checking outdated containers in the scene +- _(maya)_ option to publish and load arnold standin sequence [PYPE-579, PYPS-54] + +**fix**: +- _(pype)_ burnins were not respecting codec of the input video +- _(nuke)_ lot's of various nuke and nuke studio fixes across the board [PYPS-45] +- _(pype)_ workfiles app is not launching with the start of the app by default [PYPE-569] +- _(ftrack)_ ftrack integration during publishing was failing under certain situations [PYPS-66] +- _(pype)_ minor fixes in REST api +- _(ftrack)_ status change event was crashing when the target status was missing [PYPS-68] +- _(ftrack)_ actions will try to reconnect if they fail for some reason +- _(maya)_ problems with fps mapping when using float FPS values +- _(deadline)_ overall improvements to deadline publishing +- _(setup)_ environment variables are now remapped on the fly based on the platform pype is running on. This fixes many issues in mixed platform environments. + + + +## 2.3.6 # + +_**release date:** 27 Nov 2019_ + +**hotfix**: +- _(ftrack)_ was hiding important debug logo +- _(nuke)_ crashes during workfile publishing +- _(ftrack)_ event server crashes because of signal problems +- _(muster)_ problems with muster render submissions +- _(ftrack)_ thumbnail update event syntax errors + + +## 2.3.0 ## +_release date: 6 Oct 2019_ + +**new**: +- _(maya)_ support for yeti rigs and yeti caches +- _(maya)_ validator for comparing arbitrary attributes against ftrack +- _(pype)_ burnins can now show current date and time +- _(muster)_ pools can now be set in render globals in maya +- _(pype)_ Rest API has been implemented in beta stage +- _(nuke)_ LUT loader has been added +- _(pype)_ rudimentary user module has been added as preparation for user management +- _(pype)_ a simple logging GUI has been added to pype tray +- _(nuke)_ nuke can now bake input process into mov +- _(maya)_ imported models now have selection handle displayed by defaulting +- _(avalon)_ it's is now possible to load multiple assets at once using loader +- _(maya)_ added ability to automatically connect yeti rig to a mesh upon loading + +**changed**: +- _(ftrack)_ event server now runs two parallel processes and is able to keep queue of events to process. +- _(nuke)_ task name is now added to all rendered subsets +- _(pype)_ adding more families to standalone publisher +- _(pype)_ standalone publisher now uses pyblish-lite +- _(pype)_ standalone publisher can now create review quicktimes +- _(ftrack)_ queries to ftrack were sped up +- _(ftrack)_ multiple ftrack action have been deprecated +- _(avalon)_ avalon upstream has been updated to 5.5.0 +- _(nukestudio)_ published transforms can now be animated +- + +**fix**: +- _(maya)_ fps popup button didn't work in some cases +- _(maya)_ geometry instances and references in maya were losing shader assignments +- _(muster)_ muster rendering templates were not working correctly +- _(maya)_ arnold tx texture conversion wasn't respecting colorspace set by the artist +- _(pype)_ problems with avalon db sync +- _(maya)_ ftrack was rounding FPS making it inconsistent +- _(pype)_ wrong icon names in Creator +- _(maya)_ scene inventory wasn't showing anything if representation was removed from database after it's been loaded to the scene +- _(nukestudio)_ multiple bugs squashed +- _(loader)_ loader was taking long time to show all the loading action when first launcher in maya + +## 2.2.0 ## +_release date: 8 Sept 2019_ + +**new**: +- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts +- _(nuke)_ option to choose deadline chunk size on write nodes +- _(nukestudio)_ added option to publish soft effects (subTrackItems) from NukeStudio as subsets including LUT files. these can then be loaded in nuke or NukeStudio +- _(nuke)_ option to build nuke script from previously published latest versions of plate and render subsets. +- _(nuke)_ nuke writes now have deadline tab. +- _(ftrack)_ Prepare Project action can now be used for creating the base folder structure on disk and in ftrack, setting up all the initial project attributes and it automatically prepares `pype_project_config` folder for the given project. +- _(clockify)_ Added support for time tracking in clockify. This currently in addition to ftrack time logs, but does not completely replace them. +- _(pype)_ any attributes in Creator and Loader plugins can now be customised using pype preset system + +**changed**: +- nukestudio now uses workio API for workfiles +- _(maya)_ "FIX FPS" prompt in maya now appears in the middle of the screen +- _(muster)_ can now be configured with custom templates +- _(pype)_ global publishing plugins can now be configured using presets as well as host specific ones + + +**fix**: +- wrong version retrieval from path in certain scenarios +- nuke reset resolution wasn't working in certain scenarios + +## 2.1.0 ## +_release date: 6 Aug 2019_ + +A large cleanup release. Most of the change are under the hood. + +**new**: +- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts +- _(pype)_ Added configurable option to add burnins to any generated quicktimes +- _(ftrack)_ Action that identifies what machines pype is running on. +- _(system)_ unify subprocess calls +- _(maya)_ add audio to review quicktimes +- _(nuke)_ add crop before write node to prevent overscan problems in ffmpeg +- **Nuke Studio** publishing and workfiles support +- **Muster** render manager support +- _(nuke)_ Framerange, FPS and Resolution are set automatically at startup +- _(maya)_ Ability to load published sequences as image planes +- _(system)_ Ftrack event that sets asset folder permissions based on task assignees in ftrack. +- _(maya)_ Pyblish plugin that allow validation of maya attributes +- _(system)_ added better startup logging to tray debug, including basic connection information +- _(avalon)_ option to group published subsets to groups in the loader +- _(avalon)_ loader family filters are working now + +**changed**: +- change multiple key attributes to unify their behaviour across the pipeline + - `frameRate` to `fps` + - `startFrame` to `frameStart` + - `endFrame` to `frameEnd` + - `fstart` to `frameStart` + - `fend` to `frameEnd` + - `handle_start` to `handleStart` + - `handle_end` to `handleEnd` + - `resolution_width` to `resolutionWidth` + - `resolution_height` to `resolutionHeight` + - `pixel_aspect` to `pixelAspect` + +- _(nuke)_ write nodes are now created inside group with only some attributes editable by the artist +- rendered frames are now deleted from temporary location after their publishing is finished. +- _(ftrack)_ RV action can now be launched from any entity +- after publishing only refresh button is now available in pyblish UI +- added context instance pyblish-lite so that artist knows if context plugin fails +- _(avalon)_ allow opening selected files using enter key +- _(avalon)_ core updated to v5.2.9 with our forked changes on top + +**fix**: +- faster hierarchy retrieval from db +- _(nuke)_ A lot of stability enhancements +- _(nuke studio)_ A lot of stability enhancements +- _(nuke)_ now only renders a single write node on farm +- _(ftrack)_ pype would crash when launcher project level task +- work directory was sometimes not being created correctly +- major pype.lib cleanup. Removing of unused functions, merging those that were doing the same and general house cleaning. +- _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/HISTORY.md b/HISTORY.md index 4b5463c924..d60bd7b0c7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,3 @@ -# Pype changelog # -Welcome to pype changelog - ## 2.11.0 ## From 431a52689ebdd91e7c1047a815da3a1c277b5f1a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 10:25:34 +0200 Subject: [PATCH 826/947] discard changes fix --- .../config_setting/config_setting/widgets/inputs.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4a71f0380c..afdcc207cf 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -306,14 +306,12 @@ class InputObject(ConfigObject): def discard_changes(self): self._is_overriden = self._was_overriden - if ( - self.is_overidable - and self._was_overriden - and self.override_value is not NOT_SET - ): - self.set_value(self.override_value) + self._has_studio_override = self._had_studio_override + if self.is_overidable: + if self._was_overriden and self.override_value is not NOT_SET: + self.set_value(self.override_value) else: - if self.has_studio_override: + if self._had_studio_override: self.set_value(self.studio_value) else: self.set_value(self.default_value) From 729b474f20332f3d5ebc7132b9b0675ade7c1662 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 10:39:15 +0200 Subject: [PATCH 827/947] restart states on value updates --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index afdcc207cf..c9c40f251c 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -201,6 +201,7 @@ class ConfigObject(AbstractConfigObject): class InputObject(ConfigObject): def update_default_values(self, parent_values): + self._state = None value = NOT_SET if self._as_widget: value = parent_values @@ -218,6 +219,7 @@ class InputObject(ConfigObject): self.set_value(value) def update_studio_values(self, parent_values): + self._state = None value = NOT_SET if self._as_widget: value = parent_values From 50aea9f15061129cb0fcdeb3cb6d0db252b11229 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 10:40:01 +0200 Subject: [PATCH 828/947] apply_overrides works for widget inputs --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c9c40f251c..945ad0f255 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -285,7 +285,9 @@ class InputObject(ConfigObject): self._is_modified = False self._state = None - if parent_values is NOT_SET or self.key not in parent_values: + if self._as_widget: + override_value = parent_values + elif parent_values is NOT_SET or self.key not in parent_values: override_value = NOT_SET else: override_value = parent_values[self.key] From 6ed724d040bf85fa0dadbcc64699caa65fc124ec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 10:56:08 +0200 Subject: [PATCH 829/947] state for disabled widgets is different --- .../config_setting/widgets/inputs.py | 107 +++++++++++------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 945ad0f255..eea33d3ef6 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -412,12 +412,15 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.checkbox.setChecked(value) def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) if self._state == state: return @@ -486,12 +489,15 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.input_field.setValue(value) def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) if self._state == state: return @@ -563,12 +569,15 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.text_input.setText(value) def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) if self._state == state: return @@ -637,12 +646,16 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): super(PathInput, self).focusOutEvent(event) def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: return @@ -776,12 +789,16 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: return @@ -1036,12 +1053,15 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) if self._state == state: return @@ -1328,12 +1348,15 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - state = self.style_state( - self.has_studio_override, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if self._as_widget and not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) if self._state == state: return From c55d2ee91f589522662e21b8e34c66efa91f5d20 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 10:56:38 +0200 Subject: [PATCH 830/947] list items has apply_overrides and update_studio_values --- .../config_setting/config_setting/widgets/inputs.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index eea33d3ef6..ae55679d73 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -902,6 +902,12 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def mouseReleaseEvent(self, event): return QtWidgets.QWidget.mouseReleaseEvent(self, event) + def update_studio_values(self, value): + self.value_input.update_studio_values(value) + + def apply_overrides(self, value): + self.value_input.apply_overrides(value) + class ListWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) @@ -1002,7 +1008,11 @@ class ListWidget(QtWidgets.QWidget, InputObject): # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if value is not None: - item_widget.value_input.update_studio_values(value) + if self._is_overriden: + item_widget.apply_overrides(value) + else: + item_widget.update_studio_values(value) + self.hierarchical_style_update() else: self._on_value_change() self.updateGeometry() From 1164d0b9880716c231d05df679be21e7966d15e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:33:47 +0200 Subject: [PATCH 831/947] moved setting attributes much earlier --- pype/tools/config_setting/config_setting/widgets/inputs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ae55679d73..7096f70ecd 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -228,14 +228,15 @@ class InputObject(ConfigObject): self.studio_value = value if value is not NOT_SET: - self.set_value(value) self._has_studio_override = True + self._had_studio_override = True + self.set_value(value) else: - self.set_value(self.default_value) self._has_studio_override = False + self._had_studio_override = False + self.set_value(self.default_value) - self._had_studio_override = bool(self._has_studio_override) self._is_modified = False def _on_value_change(self, item=None): From c210b317042ae2dbab6e12b3fb027dde6c363330 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:34:05 +0200 Subject: [PATCH 832/947] is_modified cares if is_overridable or not --- .../config_setting/config_setting/widgets/inputs.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 7096f70ecd..8aebb6dd5f 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -56,11 +56,13 @@ class ConfigObject(AbstractConfigObject): @property def is_modified(self): """Has object any changes that require saving.""" - return ( - self._is_modified - or (self.was_overriden != self.is_overriden) - or (self.has_studio_override != self.had_studio_override) - ) + if self._is_modified: + return True + + if self.is_overidable: + return self.was_overriden != self.is_overriden + else: + return self.has_studio_override != self.had_studio_override @property def is_overriden(self): From adc086ba0f14ffc8f41c9404f70349e59874dfe2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:34:29 +0200 Subject: [PATCH 833/947] method apply_overrides moved --- .../config_setting/widgets/inputs.py | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8aebb6dd5f..a7e09abb6d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -239,7 +239,32 @@ class InputObject(ConfigObject): self._had_studio_override = False self.set_value(self.default_value) + def apply_overrides(self, parent_values): self._is_modified = False + self._state = None + self._had_studio_override = bool(self._has_studio_override) + if self._as_widget: + override_value = parent_values + elif parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + + self.override_value = override_value + + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -284,33 +309,6 @@ class InputObject(ConfigObject): self._is_overriden = False self._is_modified = False - def apply_overrides(self, parent_values): - self._is_modified = False - self._state = None - - if self._as_widget: - override_value = parent_values - elif parent_values is NOT_SET or self.key not in parent_values: - override_value = NOT_SET - else: - override_value = parent_values[self.key] - - self.override_value = override_value - - if override_value is NOT_SET: - self._is_overriden = False - self._was_overriden = False - if self.has_studio_override: - value = self.studio_value - else: - value = self.default_value - else: - self._is_overriden = True - self._was_overriden = True - value = override_value - - self.set_value(value) - def discard_changes(self): self._is_overriden = self._was_overriden self._has_studio_override = self._had_studio_override From 2e9ef743536ed41be674e8cb2aaa7e4499d15947 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:36:43 +0200 Subject: [PATCH 834/947] as widget input does not care about overrides --- .../config_setting/widgets/inputs.py | 85 ++++++++++++++++--- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index a7e09abb6d..81218855c9 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -413,8 +413,16 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): self.checkbox.setChecked(value) def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -490,8 +498,16 @@ class NumberWidget(QtWidgets.QWidget, InputObject): self.input_field.setValue(value) def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -570,8 +586,16 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.text_input.setText(value) def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -579,6 +603,7 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.is_overriden, self.is_modified ) + if self._state == state: return @@ -647,8 +672,16 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): super(PathInput, self).focusOutEvent(event) def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -790,8 +823,16 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -1064,8 +1105,16 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -1359,8 +1408,16 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - if self._as_widget and not self.isEnabled(): - state = self.style_state(False, False, False, False) + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, From 78dc9b1c3bedcc049d1904c0ca2b91ab5dbbfc08 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:37:34 +0200 Subject: [PATCH 835/947] list has updating default values --- .../config_setting/config_setting/widgets/inputs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 81218855c9..3f59f751e5 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -204,6 +204,8 @@ class ConfigObject(AbstractConfigObject): class InputObject(ConfigObject): def update_default_values(self, parent_values): self._state = None + self._is_modified = False + value = NOT_SET if self._as_widget: value = parent_values @@ -222,6 +224,8 @@ class InputObject(ConfigObject): def update_studio_values(self, parent_values): self._state = None + self._is_modified = False + value = NOT_SET if self._as_widget: value = parent_values @@ -944,6 +948,9 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def mouseReleaseEvent(self, event): return QtWidgets.QWidget.mouseReleaseEvent(self, event) + def update_default_values(self, value): + self.value_input.update_default_values(value) + def update_studio_values(self, value): self.value_input.update_studio_values(value) @@ -1050,7 +1057,9 @@ class ListWidget(QtWidgets.QWidget, InputObject): # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if value is not None: - if self._is_overriden: + if not self._has_studio_override: + item_widget.update_default_values(value) + elif self._is_overriden: item_widget.apply_overrides(value) else: item_widget.update_studio_values(value) From 442e5ccfd3099524f9c204d200905278e531202d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:41:43 +0200 Subject: [PATCH 836/947] fixed any_parent_is_group attribute in anatomy inputs --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 43146f7442..525e1bcd88 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -46,7 +46,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self._child_state = None self._state = None - self.any_parent_is_group = False + self._any_parent_is_group = False self.root_widget = RootsWidget(self) self.templates_widget = TemplatesWidget(self) @@ -190,7 +190,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._multiroot_state = None self._is_group = True - self.any_parent_is_group = False + self._any_parent_is_group = False self.global_is_multiroot = False self.was_multiroot = NOT_SET @@ -492,7 +492,7 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): self._state = None self._is_group = True - self.any_parent_is_group = False + self._any_parent_is_group = False self.key = "templates" body_widget = ExpandingWidget("Templates", self) From e8459167a8aaa4626e7d5c0e21ae8ad881584524 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 11:41:51 +0200 Subject: [PATCH 837/947] abstracted updating methods --- .../config_setting/widgets/widgets.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index d803624850..d7631e6fea 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -234,6 +234,21 @@ class AbstractConfigObject: ) return super(AbstractConfigObject, self).__getattribute__(name) + def update_default_values(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `update_default_values`".format(self) + ) + + def update_studio_values(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `update_studio_values`".format(self) + ) + + def apply_overrides(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `apply_overrides`".format(self) + ) + @property def is_modified(self): """Has object any changes that require saving.""" From 36fff023017c835927b5b0af256aa9a6b5d465cb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 12:00:43 +0200 Subject: [PATCH 838/947] PathWIdget has update values methods --- .../config_setting/widgets/inputs.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 3f59f751e5..8e5e8c2a5d 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2187,6 +2187,49 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.setFocusProxy(self.input_fields[0]) self.content_layout.addWidget(proxy_widget) + def update_default_values(self, parent_values): + self._state = None + self._child_state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if value is NOT_SET: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + + self.default_value = value + self._has_studio_override = False + self._had_studio_override = False + self.set_value(value) + + def update_studio_values(self, parent_values): + self._state = None + self._child_state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self.studio_value = value + if value is not NOT_SET: + self._has_studio_override = True + self._had_studio_override = True + self.set_value(value) + + else: + self._has_studio_override = False + self._had_studio_override = False + self.set_value(self.default_value) + def apply_overrides(self, parent_values): self._is_modified = False self._state = None From 42dcad9655e03655e0724a2e36f6eb8ce203ef9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 12:00:59 +0200 Subject: [PATCH 839/947] fixed update defaults in anatomy widget --- .../config_setting/widgets/anatomy_inputs.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 525e1bcd88..4756ac25a4 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -75,7 +75,15 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.value_changed.connect(self._on_value_change) - def update_default_values(self, value): + def update_default_values(self, parent_values): + self._state = None + self._child_state = None + + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + self.root_widget.update_default_values(value) self.templates_widget.update_default_values(value) @@ -280,11 +288,17 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(is_multiroot) if is_multiroot: - self.singleroot_widget.update_studio_values(NOT_SET) - self.multiroot_widget.update_studio_values(value) + for _value in value.values(): + singleroot_value = _value + break + + multiroot_value = value else: - self.singleroot_widget.update_studio_values(value) - self.multiroot_widget.update_studio_values(NOT_SET) + singleroot_value = value + multiroot_value = {"": value} + + self.singleroot_widget.update_default_values(singleroot_value) + self.multiroot_widget.update_default_values(multiroot_value) def update_studio_values(self, parent_values): self._state = None From c31267906fc7fe5ac7427b5012e7a650936120fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 12:08:17 +0200 Subject: [PATCH 840/947] added studio overrides to anatomy inputs --- .../config_setting/widgets/anatomy_inputs.py | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 4756ac25a4..141feee61d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -123,10 +123,14 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.value_changed.emit(self) def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override child_modified = self.child_modified child_invalid = self.child_invalid child_state = self.style_state( - child_invalid, self.child_overriden, child_modified + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified ) if child_state: child_state = "child-{}".format(child_state) @@ -143,6 +147,13 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.hierarchical_style_update() self.update_style() + @property + def child_has_studio_override(self): + return ( + self.root_widget.child_has_studio_override + or self.templates_widget.child_has_studio_override + ) + @property def child_modified(self): return ( @@ -320,6 +331,13 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.was_multiroot = is_multiroot self.set_multiroot(is_multiroot) + if value is NOT_SET: + self._has_studio_override = False + self._had_studio_override = False + else: + self._has_studio_override = True + self._had_studio_override = True + if is_multiroot: self.singleroot_widget.update_studio_values(NOT_SET) self.multiroot_widget.update_studio_values(value) @@ -368,6 +386,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def update_style(self): multiroot_state = self.style_state( + self.has_studio_override, False, self.is_overriden, self.was_multiroot != self.is_multiroot @@ -378,7 +397,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._multiroot_state = multiroot_state state = self.style_state( - self.is_invalid, self.is_overriden, self.is_modified + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified ) if self._state == state: return @@ -433,6 +455,13 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._on_value_change() + @property + def child_has_studio_override(self): + if self.is_multiroot: + return self.multiroot_widget.child_has_studio_override + else: + return self.singleroot_widget.child_has_studio_override + @property def child_modified(self): if self.is_multiroot: @@ -549,7 +578,10 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def update_style(self): state = self.style_state( - self.child_invalid, self.child_overriden, self.child_modified + self.has_studio_override, + self.child_invalid, + self.child_overriden, + self.child_modified ) if self._state == state: return @@ -577,6 +609,10 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def is_overriden(self): return self._is_overriden + @property + def child_has_studio_override(self): + return self.value_input.child_has_studio_override + @property def child_modified(self): return self.value_input.child_modified From c618cd0b4421a32412fee2806a93db72673cb1cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 16:01:23 +0200 Subject: [PATCH 841/947] modified pathwidget --- .../config_setting/widgets/inputs.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 8e5e8c2a5d..c32d45246b 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1197,7 +1197,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.key_input.textChanged.connect(self._on_value_change) self.value_input.value_changed.connect(self._on_value_change) - self.origin_key = self.key_value() + self.origin_key = NOT_SET def key_value(self): return self.key_input.text() @@ -1214,6 +1214,11 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): self.update_style() self.value_changed.emit(self) + def update_default_values(self, key, value): + self.origin_key = key + self.key_input.setText(key) + self.value_input.update_default_values(value) + def update_studio_values(self, key, value): self.origin_key = key self.key_input.setText(key) @@ -1488,7 +1493,9 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: - if self._is_overriden: + if not self._has_studio_override: + item_widget.update_default_values(key, value) + elif self._is_overriden: item_widget.apply_overrides(key, value) else: item_widget.update_studio_values(key, value) @@ -2206,7 +2213,12 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.default_value = value self._has_studio_override = False self._had_studio_override = False - self.set_value(value) + + if not self.multiplatform: + self.input_fields[0].update_studio_values(value) + else: + for input_field in self.input_fields: + input_field.update_studio_values(value) def update_studio_values(self, parent_values): self._state = None @@ -2223,12 +2235,16 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): if value is not NOT_SET: self._has_studio_override = True self._had_studio_override = True - self.set_value(value) - else: self._has_studio_override = False self._had_studio_override = False - self.set_value(self.default_value) + value = self.default_value + + if not self.multiplatform: + self.input_fields[0].update_studio_values(value) + else: + for input_field in self.input_fields: + input_field.update_studio_values(value) def apply_overrides(self, parent_values): self._is_modified = False From 3e5b0cf82f0a547d0f2668d455e5357d02b4eafa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 16:01:41 +0200 Subject: [PATCH 842/947] fixed saving in bases --- .../config_setting/widgets/base.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index a143e584dd..a8bdd9b1a4 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -429,14 +429,15 @@ class ProjectWidget(QtWidgets.QWidget): self._save_overrides() def _save_overrides(self): - _data = {} + data = {} for item in self.input_fields: value, is_group = item.overrides() if value is not lib.NOT_SET: - _data.update(value) + data.update(value) - data = _data.get("project") or {} - output_data = lib.convert_gui_data_to_overrides(data) + output_data = lib.convert_gui_data_to_overrides( + data.get("project") or {} + ) overrides_json_path = path_to_project_overrides( self.project_name @@ -452,21 +453,27 @@ class ProjectWidget(QtWidgets.QWidget): self._on_project_change() def _save_defaults(self): - _data = {} + data = {} for input_field in self.input_fields: value, is_group = input_field.studio_overrides() if value is not lib.NOT_SET: - _data.update(value) + data.update(value) - output = lib.convert_gui_data_to_overrides(_data.get("project", {})) + output_data = lib.convert_gui_data_to_overrides( + data.get("project", {}) + ) dirpath = os.path.dirname(PROJECT_CONFIGURATIONS_PATH) if not os.path.exists(dirpath): os.makedirs(dirpath) print("Saving data to:", PROJECT_CONFIGURATIONS_PATH) - with open(PROJECT_CONFIGURATIONS_PATH, "w") as file_stream: - json.dump(output, file_stream, indent=4) + try: + with open(PROJECT_CONFIGURATIONS_PATH, "w") as file_stream: + json.dump(output_data, file_stream, indent=4) + except Exception as exc: + print(output_data) + raise self._update_values() From 25f3d91b6d6a60eeee3b0eaad0c3258a093b4ec0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 16:01:53 +0200 Subject: [PATCH 843/947] fixed anatomy inputs filling --- .../config_setting/widgets/anatomy_inputs.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 141feee61d..e5f0dd5990 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -1,7 +1,7 @@ from Qt import QtWidgets, QtCore from .widgets import ExpandingWidget from .inputs import ConfigObject, ModifiableDict, PathWidget, RawJsonWidget -from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET +from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET, METADATA_KEY class AnatomyWidget(QtWidgets.QWidget, ConfigObject): @@ -192,6 +192,17 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): output.update(self.templates_widget.config_value()) return output + def studio_overrides(self): + if ( + self.root_widget.child_has_studio_override + or self.templates_widget.child_has_studio_override + ): + groups = [self.root_widget.key, self.templates_widget.key] + value = self.config_value() + value[self.key][METADATA_KEY] = {"groups": groups} + return value, True + return NOT_SET, False + def config_value(self): return {self.key: self.item_value()} @@ -339,11 +350,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._had_studio_override = True if is_multiroot: - self.singleroot_widget.update_studio_values(NOT_SET) self.multiroot_widget.update_studio_values(value) else: self.singleroot_widget.update_studio_values(value) - self.multiroot_widget.update_studio_values(NOT_SET) def apply_overrides(self, parent_values): # Make sure this is set to False @@ -371,13 +380,11 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if is_multiroot: self._is_overriden = parent_values is not NOT_SET self._was_overriden = bool(self._is_overriden) - self.singleroot_widget.apply_overrides(NOT_SET) self.multiroot_widget.apply_overrides(parent_values) else: self._is_overriden = value is not NOT_SET self._was_overriden = bool(self._is_overriden) self.singleroot_widget.apply_overrides(value) - self.multiroot_widget.apply_overrides(NOT_SET) def hierarchical_style_update(self): self.singleroot_widget.hierarchical_style_update() From f2372c27b9e873b0008a8d0484888ba73ff5f4b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 17:02:04 +0200 Subject: [PATCH 844/947] fixed roots input statuses --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index e5f0dd5990..bf2477bd7c 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -292,6 +292,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def update_default_values(self, parent_values): self._state = None self._multiroot_state = None + self._is_modified = False if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) @@ -309,6 +310,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.was_multiroot = is_multiroot self.set_multiroot(is_multiroot) + self._has_studio_override = False + self._had_studio_override = False if is_multiroot: for _value in value.values(): singleroot_value = _value @@ -325,6 +328,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def update_studio_values(self, parent_values): self._state = None self._multiroot_state = None + self._is_modified = False if isinstance(parent_values, dict): value = parent_values.get(self.key, NOT_SET) @@ -597,7 +601,7 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): child_state = "child-{}".format(state) else: child_state = "" - print(child_state) + self.body_widget.side_line_widget.setProperty("state", child_state) self.body_widget.side_line_widget.style().polish( self.body_widget.side_line_widget From 5aa922026d338c487a13c0e12642e47741319511 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 17:18:39 +0200 Subject: [PATCH 845/947] templates widget has value_changed signal --- .../config_setting/widgets/anatomy_inputs.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index bf2477bd7c..404fe589d3 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -74,6 +74,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.label_widget = body_widget.label_widget self.root_widget.value_changed.connect(self._on_value_change) + self.templates_widget.value_changed.connect(self._on_value_change) def update_default_values(self, parent_values): self._state = None @@ -538,6 +539,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): class TemplatesWidget(QtWidgets.QWidget, ConfigObject): + value_changed = QtCore.Signal(object) + def __init__(self, parent): super(TemplatesWidget, self).__init__(parent) @@ -571,6 +574,13 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): layout.addWidget(body_widget) + self.value_input.value_changed.connect(self._on_value_change) + + def _on_value_change(self, item): + self.update_style() + + self.value_changed.emit(self) + def update_default_values(self, values): self._state = None self.value_input.update_default_values(values) @@ -620,6 +630,10 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def is_overriden(self): return self._is_overriden + @property + def has_studio_override(self): + return self.value_input.has_studio_override + @property def child_has_studio_override(self): return self.value_input.child_has_studio_override From 5928990cb3159395bd7521a0335e256fd3d21a32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 17:18:51 +0200 Subject: [PATCH 846/947] just small modification --- pype/tools/config_setting/config_setting/widgets/inputs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c32d45246b..05fd9e8aab 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -816,8 +816,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): def update_studio_values(self, parent_values): self._is_invalid = self.text_input.has_invalid_value() - - super(RawJsonWidget, self).update_studio_values(parent_values) + return super(RawJsonWidget, self).update_studio_values(parent_values) def set_value(self, value): self.text_input.set_value(value) From d020503f53e01983b4184ed2c5fc6b5418f50e29 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 17:53:41 +0200 Subject: [PATCH 847/947] ModifiableDict has fixed invalid state --- .../config_setting/config_setting/widgets/inputs.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 05fd9e8aab..5763738351 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1337,6 +1337,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if as_widget: main_layout.addWidget(content_widget) + body_widget = None else: body_widget = ExpandingWidget(input_data["label"], self) main_layout.addWidget(body_widget) @@ -1353,6 +1354,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if expanded: body_widget.toggle_content() + self.body_widget = body_widget self.content_widget = content_widget self.content_layout = content_layout @@ -1427,7 +1429,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): else: state = self.style_state( False, - self._is_invalid, + self.is_invalid, False, self._is_modified ) @@ -1446,8 +1448,11 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): else: child_state = "" - self.setProperty("state", child_state) - self.style().polish(self) + if self.body_widget: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) if not self._as_widget: self.label_widget.setProperty("state", state) From 139c1b50cf417cd2ac313f9bb77f74422ff57f32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 18:02:07 +0200 Subject: [PATCH 848/947] fixed style updates for root widget --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 404fe589d3..efba588692 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -410,7 +410,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): state = self.style_state( self.has_studio_override, - self.is_invalid, + self.child_invalid, self.is_overriden, self.is_modified ) @@ -453,6 +453,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): or self.child_modified ) + self.update_style() + self.value_changed.emit(self) def set_multiroot(self, is_multiroot=None): From a2d3072fd6c5b52b6ea10e6f8484b7e6a1ff2643 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 10 Sep 2020 18:02:55 +0200 Subject: [PATCH 849/947] extract render was duplicating extract review --- .../publish/integrate_ftrack_instances.py | 10 +++- .../plugins/harmony/publish/extract_render.py | 59 +++---------------- 2 files changed, 17 insertions(+), 52 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index 9ef82cbc4b..2646dc90cc 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -88,8 +88,14 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): instance.data["frameEnd"] - instance.data["frameStart"] ) - if not comp.get('fps'): - comp['fps'] = instance.context.data['fps'] + fps = comp.get('fps') + if fps is None: + fps = instance.data.get( + "fps", instance.context.data['fps'] + ) + + comp['fps'] = fps + location = self.get_ftrack_location( 'ftrack.server', ft_session ) diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 45b52e0307..6486c13d70 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -4,6 +4,7 @@ import subprocess import pyblish.api from avalon import harmony +import pype.lib import clique @@ -43,6 +44,8 @@ class ExtractRender(pyblish.api.InstancePlugin): frame_start = result[4] frame_end = result[5] audio_path = result[6] + instance.data["audio"] = [{"filename": audio_path}] + instance.data["fps"] = frame_rate # Set output path to temp folder. path = tempfile.mkdtemp() @@ -87,17 +90,13 @@ class ExtractRender(pyblish.api.InstancePlugin): if len(list(col)) > 1: collection = col else: - # assert len(collections) == 1, ( - # "There should only be one image sequence in {}. Found: {}".format( - # path, len(collections) - # ) - # ) collection = collections[0] # Generate thumbnail. thumbnail_path = os.path.join(path, "thumbnail.png") + ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") args = [ - "ffmpeg", "-y", + ffmpeg_path, "-y", "-i", os.path.join(path, list(collections[0])[0]), "-vf", "scale=300:-1", "-vframes", "1", @@ -117,57 +116,17 @@ class ExtractRender(pyblish.api.InstancePlugin): self.log.debug(output.decode("utf-8")) - # Generate mov. - mov_path = os.path.join(path, instance.data["name"] + ".mov") - if os.path.isfile(audio_path): - args = [ - "ffmpeg", "-y", - "-i", audio_path, - "-i", - os.path.join(path, collection.head + "%04d" + collection.tail), - mov_path - ] - else: - args = [ - "ffmpeg", "-y", - "-i", - os.path.join(path, collection.head + "%04d" + collection.tail), - mov_path - ] - - process = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - stdin=subprocess.PIPE - ) - - output = process.communicate()[0] - - if process.returncode != 0: - raise ValueError(output.decode("utf-8")) - - self.log.debug(output.decode("utf-8")) - # Generate representations. extension = collection.tail[1:] representation = { "name": extension, "ext": extension, "files": list(collection), - "stagingDir": path - } - movie = { - "name": "mov", - "ext": "mov", - "files": os.path.basename(mov_path), "stagingDir": path, - "frameStart": frame_start, - "frameEnd": frame_end, - "fps": frame_rate, - "preview": True, - "tags": ["review", "ftrackreview"] + "tags": ["review"], + "fps": frame_rate } + thumbnail = { "name": "thumbnail", "ext": "png", @@ -175,7 +134,7 @@ class ExtractRender(pyblish.api.InstancePlugin): "stagingDir": path, "tags": ["thumbnail"] } - instance.data["representations"] = [representation, movie, thumbnail] + instance.data["representations"] = [representation, thumbnail] # Required for extract_review plugin (L222 onwards). instance.data["frameStart"] = frame_start From 81b328977d4ef701671c5567b657c1c92ac51015 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 10 Sep 2020 20:54:11 +0200 Subject: [PATCH 850/947] allow burnin in harmony --- pype/plugins/global/publish/extract_burnin.py | 3 ++- pype/plugins/harmony/publish/extract_render.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index dedfd98979..4443cfe223 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -25,7 +25,8 @@ class ExtractBurnin(pype.api.Extractor): "shell", "nukestudio", "premiere", - "standalonepublisher" + "standalonepublisher", + "harmony" ] optional = True diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 6486c13d70..70dceb9ca2 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -44,7 +44,8 @@ class ExtractRender(pyblish.api.InstancePlugin): frame_start = result[4] frame_end = result[5] audio_path = result[6] - instance.data["audio"] = [{"filename": audio_path}] + if audio_path: + instance.data["audio"] = [{"filename": audio_path}] instance.data["fps"] = frame_rate # Set output path to temp folder. From c8a6837d8927c0b251a5c3c63ecbf90d2d935c53 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 10 Sep 2020 21:30:25 +0200 Subject: [PATCH 851/947] removed activate_project and replaced with setting Session attribute --- pype/tools/standalonepublish/widgets/widget_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/standalonepublish/widgets/widget_asset.py b/pype/tools/standalonepublish/widgets/widget_asset.py index c468c9627b..6f041a535f 100644 --- a/pype/tools/standalonepublish/widgets/widget_asset.py +++ b/pype/tools/standalonepublish/widgets/widget_asset.py @@ -240,7 +240,7 @@ class AssetWidget(QtWidgets.QWidget): self.combo_projects.clear() if len(projects) > 0: self.combo_projects.addItems(projects) - self.dbcon.activate_project(projects[0]) + self.dbcon.Session["AVALON_PROJECT"] = projects[0] def on_project_change(self): projects = list() @@ -248,7 +248,7 @@ class AssetWidget(QtWidgets.QWidget): projects.append(project['name']) project_name = self.combo_projects.currentText() if project_name in projects: - self.dbcon.activate_project(project_name) + self.dbcon.Session["AVALON_PROJECT"] = project_name self.refresh() def _refresh_model(self): From b161dda7adea6a43b2e58f99a29823dddd7ea255 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 11:27:13 +0200 Subject: [PATCH 852/947] fix config loading --- pype/configurations/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/configurations/config.py b/pype/configurations/config.py index e19a27f33c..ed7104427f 100644 --- a/pype/configurations/config.py +++ b/pype/configurations/config.py @@ -14,7 +14,7 @@ def system_configurations(): def project_configurations(project_name): - default_values = default_configuration() + default_values = default_configuration()["project_configurations"] studio_values = studio_project_configurations() studio_overrides = apply_overrides(default_values, studio_values) From 2f87b60f2e2dd967a465682f9420a030564bcb99 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 11:27:44 +0200 Subject: [PATCH 853/947] add develop button to save as default --- pype/tools/config_setting/__main__.py | 3 ++- .../config_setting/widgets/base.py | 23 +++++++++++++++++-- .../config_setting/widgets/window.py | 6 ++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/__main__.py b/pype/tools/config_setting/__main__.py index aa6f707443..0e4ab1c0aa 100644 --- a/pype/tools/config_setting/__main__.py +++ b/pype/tools/config_setting/__main__.py @@ -11,7 +11,8 @@ if __name__ == "__main__": app.setStyleSheet(stylesheet) app.setWindowIcon(QtGui.QIcon(config_setting.style.app_icon_path())) - widget = config_setting.MainWidget() + develop = "-dev" in sys.argv + widget = config_setting.MainWidget(develop) widget.show() sys.exit(app.exec_()) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index a8bdd9b1a4..52d6b73c98 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -22,9 +22,10 @@ class SystemWidget(QtWidgets.QWidget): is_group = _is_group = False any_parent_is_group = _any_parent_is_group = False - def __init__(self, parent=None): + def __init__(self, develop, parent=None): super(SystemWidget, self).__init__(parent) + self.develop = develop self._ignore_value_changes = False self.input_fields = [] @@ -48,6 +49,11 @@ class SystemWidget(QtWidgets.QWidget): footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) + if self.develop: + save_as_default_btn = QtWidgets.QPushButton("Save as Default") + footer_layout.addWidget(save_as_default_btn, 0) + save_as_default_btn.clicked.connect(self._save_as_defaults) + save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) @@ -138,6 +144,9 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() + def _save_as_defaults(self): + print("_save_as_defaults") + def _update_values(self): self.ignore_value_changes = True @@ -293,9 +302,11 @@ class ProjectWidget(QtWidgets.QWidget): is_group = _is_group = False any_parent_is_group = _any_parent_is_group = False - def __init__(self, parent=None): + def __init__(self, develop, parent=None): super(ProjectWidget, self).__init__(parent) + self.develop = develop + self.is_overidable = False self._ignore_value_changes = False self.project_name = None @@ -320,6 +331,11 @@ class ProjectWidget(QtWidgets.QWidget): footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) + if self.develop: + save_as_default_btn = QtWidgets.QPushButton("Save as Default") + footer_layout.addWidget(save_as_default_btn, 0) + save_as_default_btn.clicked.connect(self._save_as_defaults) + save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) @@ -398,6 +414,9 @@ class ProjectWidget(QtWidgets.QWidget): item.apply_overrides(overrides) self.ignore_value_changes = False + def _save_as_defaults(self): + print("_save_as_defaults") + def _save(self): has_invalid = False for item in self.input_fields: diff --git a/pype/tools/config_setting/config_setting/widgets/window.py b/pype/tools/config_setting/config_setting/widgets/window.py index 7ad46b1a06..f8da9a196e 100644 --- a/pype/tools/config_setting/config_setting/widgets/window.py +++ b/pype/tools/config_setting/config_setting/widgets/window.py @@ -6,15 +6,15 @@ class MainWidget(QtWidgets.QWidget): widget_width = 1000 widget_height = 600 - def __init__(self, parent=None): + def __init__(self, develop, parent=None): super(MainWidget, self).__init__(parent) self.resize(self.widget_width, self.widget_height) header_tab_widget = QtWidgets.QTabWidget(parent=self) - studio_widget = SystemWidget() - project_widget = ProjectWidget() + studio_widget = SystemWidget(develop) + project_widget = ProjectWidget(develop) header_tab_widget.addTab(studio_widget, "System") header_tab_widget.addTab(project_widget, "Project") From 6539dc325c2bd9ba43265d2adc3358dbc2b5d7f2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 11:49:06 +0200 Subject: [PATCH 854/947] removed ftrack project config --- .../1_ftrack_projects_gui_schema.json | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json deleted file mode 100644 index e9937e64b8..0000000000 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_ftrack_projects_gui_schema.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "key": "ftrack", - "type": "dict", - "expandable": true, - "label": "Ftrack", - "children": [ - { - "key": "status_update", - "type": "dict", - "expandable": true, - "label": "Status updates", - "is_group": true, - "children": [ - { - "key": "Ready", - "type": "text", - "label": "Ready" - }, { - "key": "Ready2", - "type": "text", - "label": "Ready2" - } - ] - }, { - "key": "status_version_to_task", - "type": "dict", - "expandable": true, - "label": "Version status to Task status", - "is_group": true, - "children": [ - { - "key": "in progress", - "type": "text", - "label": "In Progress" - } - ] - } - ] -} From e9b9198f4384d7c068959dcaea153652dc5bee01 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 11:52:46 +0200 Subject: [PATCH 855/947] added is_file attribute back --- .../projects_schema/0_project_gui_schema.json | 13 +++- .../projects_schema/1_plugins_gui_schema.json | 15 ++++- .../system_schema/0_system_gui_schema.json | 3 +- .../1_applications_gui_schema.json | 1 + .../system_schema/1_intents_gui_schema.json | 1 + .../system_schema/1_tools_gui_schema.json | 1 + .../system_schema/1_tray_items.json | 1 + .../config_setting/widgets/lib.py | 67 +++++++++++++++++++ 8 files changed, 98 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json index 86504eefd2..52565640bc 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json @@ -4,7 +4,18 @@ "children": [ { "type": "anatomy", - "key": "anatomy" + "key": "anatomy", + "children": [ + { + "type": "anatomy_roots", + "key": "roots", + "is_file": true + }, { + "type": "anatomy_templates", + "key": "templates", + "is_file": true + } + ] }, { "type": "schema", "children": [ diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 597f51d6fd..5dad665b2d 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -15,6 +15,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [ { "type": "dict", @@ -74,6 +75,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [ { "type": "dict", @@ -113,6 +115,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [ { "type": "dict", @@ -276,6 +279,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [ { "type": "dict", @@ -350,7 +354,8 @@ }, { "type": "raw-json", "key": "workfile_build", - "label": "Workfile Build logic" + "label": "Workfile Build logic", + "is_file": true } ] }, { @@ -364,6 +369,7 @@ "expandable": true, "key": "create", "label": "Create plugins", + "is_file": true, "children": [ { "type": "dict", @@ -398,6 +404,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [ { "type": "dict", @@ -510,7 +517,8 @@ }, { "type": "raw-json", "key": "workfile_build", - "label": "Workfile Build logic" + "label": "Workfile Build logic", + "is_file": true } ] }, { @@ -524,6 +532,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [ { "type": "dict", @@ -573,6 +582,7 @@ "expandable": true, "key": "create", "label": "Creator plugins", + "is_file": true, "children": [ { "type": "dict", @@ -611,6 +621,7 @@ "expandable": true, "key": "publish", "label": "Publish plugins", + "is_file": true, "children": [] } ] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json index 0b51267f4e..bdc0158511 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json @@ -27,7 +27,8 @@ }, "is_group": true, "key": "templates_mapping", - "label": "Muster - Templates mapping" + "label": "Muster - Templates mapping", + "is_file": true }] } ] diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json index af128b138f..5ee769eed8 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json @@ -4,6 +4,7 @@ "label": "Applications", "expandable": true, "is_group": true, + "is_file": true, "children": [ { "type": "dict-form", diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json index 11b45c8409..a4b5e16fa1 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json @@ -4,6 +4,7 @@ "label": "Intent Setting", "expandable": true, "is_group": true, + "is_file": true, "children": [ { "type": "dict-modifiable", diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json index a16f00547e..bf35326d1c 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json @@ -4,6 +4,7 @@ "label": "Tools", "expandable": true, "is_group": true, + "is_file": true, "children": [ { "type": "dict-form", diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json index afa509254e..7507050900 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json @@ -4,6 +4,7 @@ "label": "Modules", "expandable": true, "is_group": true, + "is_file": true, "children": [ { "key": "item_usage", diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/config_setting/config_setting/widgets/lib.py index 6b3aa53c8f..daba87de15 100644 --- a/pype/tools/config_setting/config_setting/widgets/lib.py +++ b/pype/tools/config_setting/config_setting/widgets/lib.py @@ -80,6 +80,19 @@ def _fill_inner_schemas(schema_data, schema_collection): return schema_data +class SchemaMissingFileInfo(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Schema has missing definition of output file (\"is_file\" key)" + " for keys. [{}]" + ).format(", ".join(full_path_keys)) + super(SchemaMissingFileInfo, self).__init__(msg) + + class SchemeGroupHierarchyBug(Exception): def __init__(self, invalid): full_path_keys = [] @@ -108,6 +121,59 @@ class SchemaDuplicatedKeys(Exception): super(SchemaDuplicatedKeys, self).__init__(msg) +def file_keys_from_schema(schema_data): + output = [] + keys = [] + key = schema_data.get("key") + if key: + keys.append(key) + + for child in schema_data["children"]: + if child.get("is_file"): + _keys = copy.deepcopy(keys) + _keys.append(child["key"]) + output.append(_keys) + continue + + for result in file_keys_from_schema(child): + _keys = copy.deepcopy(keys) + _keys.extend(result) + output.append(_keys) + return output + + +def validate_all_has_ending_file(schema_data, is_top=True): + if schema_data.get("is_file"): + return None + + children = schema_data.get("children") + if not children: + return [[schema_data["key"]]] + + invalid = [] + keyless = "key" not in schema_data + for child in children: + result = validate_all_has_ending_file(child, False) + if result is None: + continue + + if keyless: + invalid.extend(result) + else: + for item in result: + new_invalid = [schema_data["key"]] + new_invalid.extend(item) + invalid.append(new_invalid) + + if not invalid: + return None + + if not is_top: + return invalid + + raise SchemaMissingFileInfo(invalid) + + def validate_is_group_is_unique_in_hierarchy( schema_data, any_parent_is_group=False, keys=None ): @@ -203,6 +269,7 @@ def validate_keys_are_unique(schema_data, keys=None): def validate_schema(schema_data): + validate_all_has_ending_file(schema_data) validate_is_group_is_unique_in_hierarchy(schema_data) validate_keys_are_unique(schema_data) From b8c15fd5144a22bf675bae4b074205e8b5ef53f0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 12:01:16 +0200 Subject: [PATCH 856/947] added develop_mode attribute to inputs --- .../config_setting/config_setting/widgets/base.py | 12 ++++++------ .../config_setting/config_setting/widgets/inputs.py | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 52d6b73c98..58e53b8c58 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -22,10 +22,10 @@ class SystemWidget(QtWidgets.QWidget): is_group = _is_group = False any_parent_is_group = _any_parent_is_group = False - def __init__(self, develop, parent=None): + def __init__(self, develop_mode, parent=None): super(SystemWidget, self).__init__(parent) - self.develop = develop + self.develop_mode = develop_mode self._ignore_value_changes = False self.input_fields = [] @@ -49,7 +49,7 @@ class SystemWidget(QtWidgets.QWidget): footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) - if self.develop: + if self.develop_mode: save_as_default_btn = QtWidgets.QPushButton("Save as Default") footer_layout.addWidget(save_as_default_btn, 0) save_as_default_btn.clicked.connect(self._save_as_defaults) @@ -302,10 +302,10 @@ class ProjectWidget(QtWidgets.QWidget): is_group = _is_group = False any_parent_is_group = _any_parent_is_group = False - def __init__(self, develop, parent=None): + def __init__(self, develop_mode, parent=None): super(ProjectWidget, self).__init__(parent) - self.develop = develop + self.develop_mode = develop_mode self.is_overidable = False self._ignore_value_changes = False @@ -331,7 +331,7 @@ class ProjectWidget(QtWidgets.QWidget): footer_widget = QtWidgets.QWidget() footer_layout = QtWidgets.QHBoxLayout(footer_widget) - if self.develop: + if self.develop_mode: save_as_default_btn = QtWidgets.QPushButton("Save as Default") footer_layout.addWidget(save_as_default_btn, 0) save_as_default_btn.clicked.connect(self._save_as_defaults) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 5763738351..5f56f39518 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -33,6 +33,10 @@ class ConfigObject(AbstractConfigObject): _any_parent_is_group = None _log = None + @property + def develop_mode(self): + return self._parent.develop_mode + @property def log(self): if self._log is None: From 68ba48bcf294c1a48958150b010e0d098444db03 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 13:29:15 +0200 Subject: [PATCH 857/947] in develop mode empty defaults won't crash the gui --- .../config_setting/widgets/inputs.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 5f56f39518..ea774d90de 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -12,6 +12,7 @@ from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET class ConfigObject(AbstractConfigObject): + default_input_value = NOT_SET allow_actions = True default_state = "" @@ -217,11 +218,20 @@ class InputObject(ConfigObject): value = parent_values.get(self.key, NOT_SET) if value is NOT_SET: - raise ValueError( - "Default value is not set. This is implementation BUG." - ) + if self.develop_mode: + value = self.default_input_value + if value is NOT_SET: + raise NotImplementedError(( + "{} Does not have implemented" + " attribute `default_input_value`" + ).format(self)) - self.default_value = value + else: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + + self._default_value = value self._has_studio_override = False self._had_studio_override = False self.set_value(value) @@ -370,6 +380,7 @@ class InputObject(ConfigObject): class BooleanWidget(QtWidgets.QWidget, InputObject): + default_input_value = True value_changed = QtCore.Signal(object) def __init__( @@ -455,6 +466,7 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): class NumberWidget(QtWidgets.QWidget, InputObject): + default_input_value = 0 value_changed = QtCore.Signal(object) input_modifiers = ("minimum", "maximum", "decimal") @@ -541,6 +553,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): class TextWidget(QtWidgets.QWidget, InputObject): + default_input_value = "" value_changed = QtCore.Signal(object) def __init__( @@ -633,6 +646,7 @@ class TextWidget(QtWidgets.QWidget, InputObject): class PathInputWidget(QtWidgets.QWidget, InputObject): + default_input_value = "" value_changed = QtCore.Signal(object) def __init__( @@ -768,6 +782,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): class RawJsonWidget(QtWidgets.QWidget, InputObject): + default_input_value = "{}" value_changed = QtCore.Signal(object) def __init__( @@ -872,9 +887,9 @@ class ListItem(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) def __init__(self, object_type, input_modifiers, config_parent, parent): - self._parent = config_parent super(ListItem, self).__init__(parent) + self._parent = config_parent layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) @@ -962,6 +977,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): class ListWidget(QtWidgets.QWidget, InputObject): + default_input_value = [] value_changed = QtCore.Signal(object) def __init__( @@ -1297,6 +1313,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): class ModifiableDict(QtWidgets.QWidget, InputObject): + default_input_value = {} # Should be used only for dictionary with one datatype as value # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) From 72221cfafd75ae167f907608faed624a497728d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 13:39:17 +0200 Subject: [PATCH 858/947] it is possible to know that defaults are not set --- .../config_setting/widgets/inputs.py | 218 +++++++----------- 1 file changed, 77 insertions(+), 141 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index ea774d90de..32befd6ef8 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -14,25 +14,58 @@ from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET class ConfigObject(AbstractConfigObject): default_input_value = NOT_SET allow_actions = True - default_state = "" - _as_widget = False - _is_group = False - # TODO not implemented yet - _is_nullable = False + def set_default_attributes(self): + # Default input attributes + self._has_studio_override = False + self._had_studio_override = False - _has_studio_override = False - _had_studio_override = False + self._is_overriden = False + self._was_overriden = False - _is_overriden = False - _was_overriden = False + self._is_modified = False + self._is_invalid = False - _is_modified = False - _is_invalid = False + self._is_nullable = False + self._as_widget = False + self._is_group = False - _any_parent_is_group = None - _log = None + self._any_parent_is_group = None + + # Parent input + self._parent = None + + # States of inputs + self._state = None + self._child_state = None + + # Attributes where values are stored + self.default_value = NOT_SET + self.studio_value = NOT_SET + self.override_value = NOT_SET + + # Log object + self._log = None + + # Only for develop mode + self.defaults_not_set = False + + def initial_attributes(self, input_data, parent, as_widget): + self.set_default_attributes() + + self._parent = parent + self._as_widget = as_widget + + self._is_group = input_data.get("is_group", False) + # TODO not implemented yet + self._is_nullable = input_data.get("is_nullable", False) + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self._any_parent_is_group = any_parent_is_group @property def develop_mode(self): @@ -61,7 +94,7 @@ class ConfigObject(AbstractConfigObject): @property def is_modified(self): """Has object any changes that require saving.""" - if self._is_modified: + if self._is_modified or self.defaults_not_set: return True if self.is_overidable: @@ -220,6 +253,7 @@ class InputObject(ConfigObject): if value is NOT_SET: if self.develop_mode: value = self.default_input_value + self.defaults_not_set = True if value is NOT_SET: raise NotImplementedError(( "{} Does not have implemented" @@ -231,7 +265,7 @@ class InputObject(ConfigObject): "Default value is not set. This is implementation BUG." ) - self._default_value = value + self.default_value = value self._has_studio_override = False self._had_studio_override = False self.set_value(value) @@ -391,22 +425,13 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): parent_widget = parent super(BooleanWidget, self).__init__(parent_widget) - self._parent = parent - self._as_widget = as_widget - self._state = None - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET + self.initial_attributes(input_data, parent, as_widget) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not as_widget: + if not self._as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] @@ -478,16 +503,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): parent_widget = parent super(NumberWidget, self).__init__(parent_widget) - self._parent = parent - self._as_widget = as_widget - self._state = None - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET + self.initial_attributes(input_data, parent, as_widget) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -564,19 +580,10 @@ class TextWidget(QtWidgets.QWidget, InputObject): parent_widget = parent super(TextWidget, self).__init__(parent_widget) - self._parent = parent - self._as_widget = as_widget - self._state = None - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) + self.initial_attributes(input_data, parent, as_widget) self.multiline = input_data.get("multiline", False) - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET - layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) @@ -657,16 +664,7 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): parent_widget = parent super(PathInputWidget, self).__init__(parent_widget) - self._parent = parent - self._as_widget = as_widget - self._state = None - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET + self.initial_attributes(input_data, parent, as_widget) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -793,22 +791,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): parent_widget = parent super(RawJsonWidget, self).__init__(parent_widget) - self._parent = parent - self._as_widget = as_widget - self._state = None - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self._any_parent_is_group = any_parent_is_group - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) - - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET + self.initial_attributes(input_data, parent, as_widget) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -889,7 +872,10 @@ class ListItem(QtWidgets.QWidget, ConfigObject): def __init__(self, object_type, input_modifiers, config_parent, parent): super(ListItem, self).__init__(parent) + self.set_default_attributes() self._parent = config_parent + self._any_parent_is_group = True + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) @@ -989,20 +975,11 @@ class ListWidget(QtWidgets.QWidget, InputObject): super(ListWidget, self).__init__(parent_widget) self.setObjectName("ListWidget") - self._parent = parent - self._state = None - self._as_widget = as_widget - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) + self.initial_attributes(input_data, parent, as_widget) self.object_type = input_data["object_type"] self.input_modifiers = input_data.get("input_modifiers") or {} - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET - self.key = input_data["key"] self.input_fields = [] @@ -1327,22 +1304,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): super(ModifiableDict, self).__init__(parent_widget) self.setObjectName("ModifiableDict") - self._parent = parent - self._state = None - self._as_widget = as_widget - - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self._any_parent_is_group = any_parent_is_group - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) + self.initial_attributes(input_data, parent, as_widget) self.input_fields = [] @@ -1573,18 +1535,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): super(DictWidget, self).__init__(parent_widget) self.setObjectName("DictWidget") - self._state = None - self._child_state = None - - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - self._any_parent_is_group = any_parent_is_group - - self._is_group = input_data.get("is_group", False) - self._is_nullable = input_data.get("is_nullable", False) + self.initial_attributes(input_data, parent, as_widget) if input_data.get("highlight_content", False): content_state = "hightlighted" @@ -1891,14 +1842,8 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): super(DictInvisible, self).__init__(parent_widget) self.setObjectName("DictInvisible") - self._parent = parent + self.initial_attributes(input_data, parent, as_widget) - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self._any_parent_is_group = any_parent_is_group - self._is_group = input_data.get("is_group", False) if self._is_group: raise TypeError("DictInvisible can't be marked as group input.") @@ -2098,7 +2043,6 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): class PathWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) - platforms = ("windows", "darwin", "linux") platform_labels_mapping = { "windows": "Windows", @@ -2120,30 +2064,17 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): parent_widget = parent super(PathWidget, self).__init__(parent_widget) - self._parent = parent - self._state = None - self._child_state = None - self._as_widget = as_widget - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - self._any_parent_is_group = any_parent_is_group + self.initial_attributes(input_data, parent, as_widget) # This is partial input and dictionary input - if not any_parent_is_group and not as_widget: + if not self.any_parent_is_group and not self._as_widget: self._is_group = True else: self._is_group = False - self._is_nullable = input_data.get("is_nullable", False) self.multiplatform = input_data.get("multiplatform", False) self.multipath = input_data.get("multipath", False) - self.default_value = NOT_SET - self.studio_value = NOT_SET - self.override_value = NOT_SET - self.input_fields = [] if not self.multiplatform and not self.multipath: @@ -2153,7 +2084,7 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not as_widget: + if not self._as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] @@ -2171,6 +2102,16 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self.create_gui() + @property + def default_input_value(self): + if self.multiplatform: + return { + platform: "" + for platform in self.platforms + } + else: + return "" + def create_gui(self): if not self.multiplatform and not self.multipath: input_data = {"key": self.key} @@ -2467,14 +2408,9 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): parent_widget = parent super(DictFormWidget, self).__init__(parent_widget) - self._parent = parent - - any_parent_is_group = parent.is_group - if not any_parent_is_group: - any_parent_is_group = parent.any_parent_is_group - - self._any_parent_is_group = any_parent_is_group + self.initial_attributes(input_data, parent, as_widget) + self._as_widget = False self._is_group = False self.input_fields = [] From 3cd46bb0f2e7045a406e25c19e23ca9cb00a6a5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 13:42:59 +0200 Subject: [PATCH 859/947] configurations lib has constant path to defaults --- pype/configurations/lib.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index 4cd7203626..b555ea0167 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -29,6 +29,9 @@ STUDIO_PROJECT_OVERRIDES_PATH = os.path.join( STUDIO_OVERRIDES_PATH, "project_overrides" ) +# Path to default configurations +DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") + # Variable where cache of default configurations are stored _DEFAULT_CONFIGURATIONS = None @@ -36,9 +39,7 @@ _DEFAULT_CONFIGURATIONS = None def default_configuration(): global _DEFAULT_CONFIGURATIONS if _DEFAULT_CONFIGURATIONS is None: - current_dir = os.path.dirname(__file__) - defaults_path = os.path.join(current_dir, "defaults") - _DEFAULT_CONFIGURATIONS = load_jsons_from_dir(defaults_path) + _DEFAULT_CONFIGURATIONS = load_jsons_from_dir(DEFAULTS_DIR) return _DEFAULT_CONFIGURATIONS From 714bd5794327785596964ee05d521a7e5b70c2d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 13:59:54 +0200 Subject: [PATCH 860/947] added specific keys as constants --- pype/configurations/lib.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index b555ea0167..c0b783e3fb 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -14,12 +14,14 @@ POP_KEY = "__pop_key__" STUDIO_OVERRIDES_PATH = os.environ["PYPE_CONFIG"] # File where studio's system overrides are stored +SYSTEM_CONFIGURATIONS_KEY = "system_configurations" SYSTEM_CONFIGURATIONS_PATH = os.path.join( - STUDIO_OVERRIDES_PATH, "system_configurations.json" + STUDIO_OVERRIDES_PATH, SYSTEM_CONFIGURATIONS_KEY + ".json" ) # File where studio's default project overrides are stored -PROJECT_CONFIGURATIONS_FILENAME = "project_configurations.json" +PROJECT_CONFIGURATIONS_KEY = "project_configurations" +PROJECT_CONFIGURATIONS_FILENAME = PROJECT_CONFIGURATIONS_KEY + ".json" PROJECT_CONFIGURATIONS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, PROJECT_CONFIGURATIONS_FILENAME ) From ebfa87a3ac2cc57108a87cdec9b11da3f342928d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 14:05:12 +0200 Subject: [PATCH 861/947] removed standalone publisher as not complete yet --- .../projects_schema/1_plugins_gui_schema.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index 5dad665b2d..df0de07a4d 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -610,21 +610,6 @@ ] } ] - }, { - "type": "dict", - "expandable": true, - "key": "standalonepublisher", - "label": "StandalonePublisher", - "children": [ - { - "type": "dict", - "expandable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [] - } - ] } ] } From ce198d1c4a3460174c16637eb8b6c04e04083351 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 14:09:26 +0200 Subject: [PATCH 862/947] added possibility to reset defaults --- pype/configurations/lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index c0b783e3fb..b8832ceacb 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -38,6 +38,11 @@ DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") _DEFAULT_CONFIGURATIONS = None +def reset_default_configurations(): + global _DEFAULT_CONFIGURATIONS + _DEFAULT_CONFIGURATIONS = None + + def default_configuration(): global _DEFAULT_CONFIGURATIONS if _DEFAULT_CONFIGURATIONS is None: From a4a4b5244f07b08099748217c86a4e15057d85e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 14:34:26 +0200 Subject: [PATCH 863/947] added ability to save as defaults --- .../global/applications.json | 31 +++--- .../projects_schema/1_plugins_gui_schema.json | 4 + .../config_setting/widgets/base.py | 94 ++++++++++++++++++- 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/pype/configurations/defaults/system_configurations/global/applications.json b/pype/configurations/defaults/system_configurations/global/applications.json index 8e27f11002..3a74a85468 100644 --- a/pype/configurations/defaults/system_configurations/global/applications.json +++ b/pype/configurations/defaults/system_configurations/global/applications.json @@ -6,34 +6,29 @@ "celaction_local": true, "celaction_remote": true, "harmony_17": true, - "houdini_16": true, - "houdini_17": true, - "houdini_18": true, - "maya_2016": true, "maya_2017": true, "maya_2018": true, "maya_2019": true, "maya_2020": true, - "nukestudio_10.0": true, - "nukestudio_11.0": true, - "nukestudio_11.2": true, - "nukestudio_11.3": true, - "nukestudio_12.0": true, - "nukex_10.0": true, - "nukex_11.0": true, - "nukex_11.2": true, - "nukex_11.3": true, - "nukex_12.0": true, "nuke_10.0": true, - "nuke_11.0": true, "nuke_11.2": true, "nuke_11.3": true, "nuke_12.0": true, - "photoshop_2020": true, + "nukex_10.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nukestudio_10.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "houdini_16": true, + "houdini_16.5": false, + "houdini_17": true, + "houdini_18": true, "premiere_2019": true, "premiere_2020": true, "resolve_16": true, "storyboardpro_7": true, - "unreal_4.24": true, - "houdini_16.5": false + "unreal_4.24": true } \ No newline at end of file diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json index df0de07a4d..c279a6b04a 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json @@ -140,6 +140,10 @@ "is_group": true, "children": [ { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "dict-invisible", "key": "ffmpeg_args", "children": [ diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 58e53b8c58..d7078e4ab6 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -2,8 +2,12 @@ import os import json from Qt import QtWidgets, QtCore, QtGui from pype.configurations.lib import ( + SYSTEM_CONFIGURATIONS_KEY, SYSTEM_CONFIGURATIONS_PATH, + PROJECT_CONFIGURATIONS_KEY, PROJECT_CONFIGURATIONS_PATH, + DEFAULTS_DIR, + reset_default_configurations, default_configuration, studio_system_configurations, project_configurations_overrides, @@ -145,7 +149,50 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() def _save_as_defaults(self): - print("_save_as_defaults") + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["system"] + + prject_defaults_dir = os.path.join( + DEFAULTS_DIR, SYSTEM_CONFIGURATIONS_KEY + ) + keys_to_file = lib.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + + output_path = os.path.join(prject_defaults_dir, subpath) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to: ", subpath) + with open(output_path, "w") as file_stream: + json.dump(new_values, file_stream, indent=4) + + reset_default_configurations() + + self._update_values() def _update_values(self): self.ignore_value_changes = True @@ -415,7 +462,50 @@ class ProjectWidget(QtWidgets.QWidget): self.ignore_value_changes = False def _save_as_defaults(self): - print("_save_as_defaults") + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["project"] + + prject_defaults_dir = os.path.join( + DEFAULTS_DIR, PROJECT_CONFIGURATIONS_KEY + ) + keys_to_file = lib.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + + output_path = os.path.join(prject_defaults_dir, subpath) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to: ", subpath) + with open(output_path, "w") as file_stream: + json.dump(new_values, file_stream, indent=4) + + reset_default_configurations() + + self._update_values() def _save(self): has_invalid = False From 438d456bb55b52158a51859ea347455aefa612a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 15:21:12 +0200 Subject: [PATCH 864/947] fix dictionary item initialization --- pype/tools/config_setting/config_setting/widgets/inputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 32befd6ef8..bc63e27f5a 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -873,6 +873,7 @@ class ListItem(QtWidgets.QWidget, ConfigObject): super(ListItem, self).__init__(parent) self.set_default_attributes() + self._parent = config_parent self._any_parent_is_group = True @@ -1149,6 +1150,7 @@ class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): def __init__(self, object_type, input_modifiers, config_parent, parent): super(ModifiableDictItem, self).__init__(parent) + self.set_default_attributes() self._parent = config_parent self.is_single = False From ea8ffca0fadbc8122e2a1b55e4712c61bfb5a33b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 15:21:34 +0200 Subject: [PATCH 865/947] modified anatomy widgets to match new initialization --- .../config_setting/widgets/anatomy_inputs.py | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index efba588692..4ae7d8d290 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -40,14 +40,11 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): ) super(AnatomyWidget, self).__init__(parent) self.setObjectName("AnatomyWidget") - self._parent = parent + + self.initial_attributes(input_data, parent, as_widget) + self.key = "anatomy" - self._child_state = None - self._state = None - - self._any_parent_is_group = False - self.root_widget = RootsWidget(self) self.templates_widget = TemplatesWidget(self) @@ -214,15 +211,13 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def __init__(self, parent): super(RootsWidget, self).__init__(parent) self.setObjectName("RootsWidget") - self._parent = parent + + input_data = {"is_group": True} + self.initial_attributes(input_data, parent, False) + self.key = "roots" - self._state = None self._multiroot_state = None - - self._is_group = True - self._any_parent_is_group = False - self.global_is_multiroot = False self.was_multiroot = NOT_SET @@ -546,12 +541,9 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def __init__(self, parent): super(TemplatesWidget, self).__init__(parent) - self._parent = parent + input_data = {"is_group": True} + self.initial_attributes(input_data, parent, False) - self._state = None - - self._is_group = True - self._any_parent_is_group = False self.key = "templates" body_widget = ExpandingWidget("Templates", self) @@ -634,7 +626,7 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): @property def has_studio_override(self): - return self.value_input.has_studio_override + return self.value_input._has_studio_override @property def child_has_studio_override(self): From f346b80e4d29b25b9406709d26a8bcf74ebceb29 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 15:27:56 +0200 Subject: [PATCH 866/947] set as overriden kinda works on roots widget --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 4ae7d8d290..2c4548df4e 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -173,6 +173,10 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): or self.templates_widget.child_invalid ) + def set_as_overriden(self): + self.root_widget.set_as_overriden() + self.templates_widget.set_as_overriden() + def remove_overrides(self): self.root_widget.remove_overrides() self.templates_widget.remove_overrides() @@ -521,7 +525,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._is_modified = self.child_modified def set_as_overriden(self): - self._is_overriden = self._was_overriden + self._is_overriden = True self.singleroot_widget.set_as_overriden() self.multiroot_widget.set_as_overriden() From 12fff9477a0d4038a3a54d7c6af144144ad675a5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 15:31:01 +0200 Subject: [PATCH 867/947] multiroot label does not care about overrides --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 2c4548df4e..7dd89838d4 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -399,7 +399,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): multiroot_state = self.style_state( self.has_studio_override, False, - self.is_overriden, + False, self.was_multiroot != self.is_multiroot ) if multiroot_state != self._multiroot_state: From c76f9f604c96b82eb26c3fb2b1fd556431803f64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 15:52:01 +0200 Subject: [PATCH 868/947] anatomy overrides can be unset --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 7dd89838d4..a62b32664e 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -186,7 +186,9 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.templates_widget.discard_changes() def overrides(self): - return self.config_value(), True + if self.child_overriden: + return self.config_value(), True + return NOT_SET, False def item_value(self): output = {} From 47f1429214b73aa6db634e3660b7666471a87bb7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 15:52:41 +0200 Subject: [PATCH 869/947] fixed different cases of set_overrides and removing overrides --- .../config_setting/widgets/anatomy_inputs.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index a62b32664e..306149f9b3 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -510,8 +510,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(self.global_is_multiroot) - self.singleroot_widget.remove_overrides() - self.multiroot_widget.remove_overrides() + if self.is_multiroot: + self.multiroot_widget.remove_overrides() + else: + self.singleroot_widget.remove_overrides() def discard_changes(self): self._is_overriden = self._was_overriden @@ -521,8 +523,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: self.set_multiroot(self.global_is_multiroot) - self.singleroot_widget.discard_changes() - self.multiroot_widget.discard_changes() + if self.is_multiroot: + self.multiroot_widget.discard_changes() + else: + self.singleroot_widget.discard_changes() self._is_modified = self.child_modified From 3759920d3873158ccf0c7b5c1ab1844734b0b869 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 16:31:40 +0200 Subject: [PATCH 870/947] modified api to not break old way of loading presets --- pype/api.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pype/api.py b/pype/api.py index ee0d1ef4a2..c5cd28d4de 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,8 +1,12 @@ -from .configurations import config +from .configurations.config import ( + system_configurations, + project_configurations +) from pypeapp import ( Logger, Anatomy, project_overrides_dir_path, + config, execute ) @@ -49,6 +53,9 @@ from .lib import ( from .lib import _subprocess as subprocess __all__ = [ + "system_configurations", + "project_configurations", + "Logger", "Anatomy", "project_overrides_dir_path", From 201d0aca14a58c419d13fcf298719ccedfb72721 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:17:54 +0200 Subject: [PATCH 871/947] moved anatomy to separate folder --- .../colorspace.json | 0 .../anatomy => project_anatomy}/dataflow.json | 0 .../anatomy => project_anatomy}/roots.json | 0 .../templates.json | 0 .../project_configurations/anatomy/README.md | 0 .../anatomy/colorspace/aces103-cg.json | 46 --------------- .../anatomy/dataflow/aces-exr.json | 58 ------------------- .../anatomy/default.yaml | 29 ---------- 8 files changed, 133 deletions(-) rename pype/configurations/defaults/{project_configurations/anatomy => project_anatomy}/colorspace.json (100%) rename pype/configurations/defaults/{project_configurations/anatomy => project_anatomy}/dataflow.json (100%) rename pype/configurations/defaults/{project_configurations/anatomy => project_anatomy}/roots.json (100%) rename pype/configurations/defaults/{project_configurations/anatomy => project_anatomy}/templates.json (100%) delete mode 100644 pype/configurations/defaults/project_configurations/anatomy/README.md delete mode 100644 pype/configurations/defaults/project_configurations/anatomy/colorspace/aces103-cg.json delete mode 100644 pype/configurations/defaults/project_configurations/anatomy/dataflow/aces-exr.json delete mode 100644 pype/configurations/defaults/project_configurations/anatomy/default.yaml diff --git a/pype/configurations/defaults/project_configurations/anatomy/colorspace.json b/pype/configurations/defaults/project_anatomy/colorspace.json similarity index 100% rename from pype/configurations/defaults/project_configurations/anatomy/colorspace.json rename to pype/configurations/defaults/project_anatomy/colorspace.json diff --git a/pype/configurations/defaults/project_configurations/anatomy/dataflow.json b/pype/configurations/defaults/project_anatomy/dataflow.json similarity index 100% rename from pype/configurations/defaults/project_configurations/anatomy/dataflow.json rename to pype/configurations/defaults/project_anatomy/dataflow.json diff --git a/pype/configurations/defaults/project_configurations/anatomy/roots.json b/pype/configurations/defaults/project_anatomy/roots.json similarity index 100% rename from pype/configurations/defaults/project_configurations/anatomy/roots.json rename to pype/configurations/defaults/project_anatomy/roots.json diff --git a/pype/configurations/defaults/project_configurations/anatomy/templates.json b/pype/configurations/defaults/project_anatomy/templates.json similarity index 100% rename from pype/configurations/defaults/project_configurations/anatomy/templates.json rename to pype/configurations/defaults/project_anatomy/templates.json diff --git a/pype/configurations/defaults/project_configurations/anatomy/README.md b/pype/configurations/defaults/project_configurations/anatomy/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pype/configurations/defaults/project_configurations/anatomy/colorspace/aces103-cg.json b/pype/configurations/defaults/project_configurations/anatomy/colorspace/aces103-cg.json deleted file mode 100644 index dd3fca4c2d..0000000000 --- a/pype/configurations/defaults/project_configurations/anatomy/colorspace/aces103-cg.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "resolve": { - - }, - "nukestudio": { - - }, - "nuke": { - "root": { - "colorManagement": "OCIO", - "OCIO_config": "aces_1.0.3", - "workingSpaceLUT": "ACES - ACEScg", - "defaultViewerLUT": "OCIO LUTs", - "monitorLut": "ACES/sRGB", - "int8Lut": "Utility - sRGB - Texture", - "int16Lut": "Utility - sRGB - Texture", - "logLut": "Input - ADX - ADX10", - "floatLut": "ACES - ACES2065-1" - }, - "viewer": { - "viewerProcess": "sRGB (ACES)" - }, - "write": { - "render": { - "colorspace": "ACES - ACES2065-1" - }, - "prerender": { - "colorspace": "ACES - ACES2065-1" - }, - "still": { - "colorspace": "Utility - Curve - sRGB" - } - }, - "read": { - "[^-a-zA-Z0-9](beauty)[^-a-zA-Z0-9]": "lin_srgb", - "[^-a-zA-Z0-9](P|N|Z|crypto)[^-a-zA-Z0-9]": "raw", - "[^-a-zA-Z0-9](plateRef)[^-a-zA-Z0-9]": "crv_srgb" - } - }, - "maya": { - - }, - "houdini": { - - } -} diff --git a/pype/configurations/defaults/project_configurations/anatomy/dataflow/aces-exr.json b/pype/configurations/defaults/project_configurations/anatomy/dataflow/aces-exr.json deleted file mode 100644 index 75846c0bd6..0000000000 --- a/pype/configurations/defaults/project_configurations/anatomy/dataflow/aces-exr.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "nuke": { - "nodes": { - "connected": true, - "modifymetadata": { - "_id": "connect_metadata", - "_previous": "ENDING", - "metadata.set.pype_studio_name": "{PYPE_STUDIO_NAME}", - "metadata.set.avalon_project_name": "{AVALON_PROJECT}", - "metadata.set.avalon_project_code": "{PYPE_STUDIO_CODE}", - "metadata.set.avalon_asset_name": "{AVALON_ASSET}" - }, - "crop": { - "_id": "connect_crop", - "_previous": "connect_metadata", - "box": [ - "{metadata.crop.x}", - "{metadata.crop.y}", - "{metadata.crop.right}", - "{metadata.crop.top}" - ] - }, - "write": { - "render": { - "_id": "output_write", - "_previous": "connect_crop", - "file_type": "exr", - "datatype": "16 bit half", - "compression": "Zip (1 scanline)", - "create_directories": true, - "autocrop": true, - "tile_color": "0xff0000ff", - "channels": "rgb" - }, - "prerender": { - "_id": "output_write", - "_previous": "connect_crop", - "file_type": "exr", - "datatype": "16 bit half", - "compression": "Zip (1 scanline)", - "create_directories": true, - "autocrop": false, - "tile_color": "0xc9892aff", - "channels": "rgba" - }, - "still": { - "_previous": "connect_crop", - "channels": "rgba", - "file_type": "tiff", - "datatype": "16 bit", - "compression": "LZW", - "create_directories": true, - "tile_color": "0x4145afff" - } - } - } - } -} diff --git a/pype/configurations/defaults/project_configurations/anatomy/default.yaml b/pype/configurations/defaults/project_configurations/anatomy/default.yaml deleted file mode 100644 index 381aa05d4a..0000000000 --- a/pype/configurations/defaults/project_configurations/anatomy/default.yaml +++ /dev/null @@ -1,29 +0,0 @@ - -version_padding: 3 -version: "v{version:0>{@version_padding}}" -frame_padding: 4 -frame: "{frame:0>{@frame_padding}}" - -work: - folder: "{root}/{project[name]}/{hierarchy}/{asset}/work/{task}" - file: "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}" - path: "{@folder}/{@file}" - -render: - folder: "{root}/{project[name]}/{hierarchy}/{asset}/publish/render/{subset}/{@version}" - file: "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}" - path: "{@folder}/{@file}" - -texture: - path: "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}" - -publish: - folder: "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}" - file: "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}" - path: "{@folder}/{@file}" - thumbnail: "{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}{ext}" - -master: - folder: "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/master" - file: "{project[code]}_{asset}_{subset}_master<_{output}><.{frame}>.{representation}" - path: "{@folder}/{@file}" From 5971468b93a22627ed08c28313642bfd7c88323d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:21:42 +0200 Subject: [PATCH 872/947] anatomy data are more schema based --- .../config_setting/widgets/anatomy_inputs.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 306149f9b3..ed73ad6468 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -43,10 +43,19 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.initial_attributes(input_data, parent, as_widget) - self.key = "anatomy" + self.key = input_data["key"] - self.root_widget = RootsWidget(self) - self.templates_widget = TemplatesWidget(self) + children_data = input_data["children"] + roots_input_data = {} + templates_input_data = {} + for child in children_data: + if child["type"] == "anatomy_roots": + roots_input_data = child + elif child["type"] == "anatomy_templates": + templates_input_data = child + + self.root_widget = RootsWidget(roots_input_data, self) + self.templates_widget = TemplatesWidget(templates_input_data, self) self.setAttribute(QtCore.Qt.WA_StyledBackground) @@ -214,14 +223,14 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): class RootsWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) - def __init__(self, parent): + def __init__(self, input_data, parent): super(RootsWidget, self).__init__(parent) self.setObjectName("RootsWidget") input_data = {"is_group": True} self.initial_attributes(input_data, parent, False) - self.key = "roots" + self.key = input_data["key"] self._multiroot_state = None self.global_is_multiroot = False @@ -548,13 +557,13 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): class TemplatesWidget(QtWidgets.QWidget, ConfigObject): value_changed = QtCore.Signal(object) - def __init__(self, parent): + def __init__(self, input_data, parent): super(TemplatesWidget, self).__init__(parent) input_data = {"is_group": True} self.initial_attributes(input_data, parent, False) - self.key = "templates" + self.key = input_data["key"] body_widget = ExpandingWidget("Templates", self) content_widget = QtWidgets.QWidget(body_widget) From 4bd3c8716bfdd5e4e7d55521d719a1569bc973f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:21:53 +0200 Subject: [PATCH 873/947] wrapped project data more inside --- .../projects_schema/0_project_gui_schema.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json index 52565640bc..3d7cac68fd 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json @@ -4,7 +4,7 @@ "children": [ { "type": "anatomy", - "key": "anatomy", + "key": "project_anatomy", "children": [ { "type": "anatomy_roots", @@ -17,9 +17,15 @@ } ] }, { - "type": "schema", + "type": "dict-invisible", + "key": "project_configurations", "children": [ - "1_plugins_gui_schema" + { + "type": "schema", + "children": [ + "1_plugins_gui_schema" + ] + } ] } ] From e359256b6ebcc100be3e8a83ebce3356a342abf4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:22:39 +0200 Subject: [PATCH 874/947] configurations has also functions for getting anatomy --- pype/configurations/lib.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index b8832ceacb..abd848f95c 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -26,6 +26,12 @@ PROJECT_CONFIGURATIONS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, PROJECT_CONFIGURATIONS_FILENAME ) +PROJECT_ANATOMY_KEY = "project_anatomy" +PROJECT_ANATOMY_FILENAME = PROJECT_ANATOMY_KEY + ".json" +PROJECT_ANATOMY_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, PROJECT_ANATOMY_FILENAME +) + # Folder where studio's per project overrides are stored STUDIO_PROJECT_OVERRIDES_PATH = os.path.join( STUDIO_OVERRIDES_PATH, "project_overrides" @@ -167,6 +173,12 @@ def studio_project_configurations(): return {} +def studio_project_anatomy(): + if os.path.exists(PROJECT_ANATOMY_PATH): + return load_json(PROJECT_ANATOMY_PATH) + return {} + + def path_to_project_overrides(project_name): return os.path.join( STUDIO_PROJECT_OVERRIDES_PATH, @@ -175,6 +187,14 @@ def path_to_project_overrides(project_name): ) +def path_to_project_anatomy(project_name): + return os.path.join( + STUDIO_PROJECT_OVERRIDES_PATH, + project_name, + PROJECT_ANATOMY_FILENAME + ) + + def project_configurations_overrides(project_name): if not project_name: return {} @@ -185,6 +205,16 @@ def project_configurations_overrides(project_name): return load_json(path_to_json) +def project_anatomy_overrides(project_name): + if not project_name: + return {} + + path_to_json = path_to_project_anatomy(project_name) + if not os.path.exists(path_to_json): + return {} + return load_json(path_to_json) + + def merge_overrides(global_dict, override_dict): if OVERRIDEN_KEY in override_dict: overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) From f5736a7e9ab1e7b7ea537df526c63aea6509f503 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:23:02 +0200 Subject: [PATCH 875/947] anatomy loading/saving is in project widget separated --- .../config_setting/widgets/base.py | 100 +++++++++++++----- 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index d7078e4ab6..6d25357fc9 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -6,13 +6,23 @@ from pype.configurations.lib import ( SYSTEM_CONFIGURATIONS_PATH, PROJECT_CONFIGURATIONS_KEY, PROJECT_CONFIGURATIONS_PATH, + PROJECT_ANATOMY_KEY, + PROJECT_ANATOMY_PATH, + DEFAULTS_DIR, + reset_default_configurations, default_configuration, + studio_system_configurations, + studio_project_configurations, + studio_project_anatomy, + project_configurations_overrides, + project_anatomy_overrides, + path_to_project_overrides, - studio_project_configurations + path_to_project_anatomy ) from .widgets import UnsavedChangesDialog from . import lib @@ -448,13 +458,22 @@ class ProjectWidget(QtWidgets.QWidget): def _on_project_change(self): project_name = self.project_list_widget.project_name() if project_name is None: - _overrides = lib.NOT_SET + _project_overrides = lib.NOT_SET + _project_anatomy = lib.NOT_SET self.is_overidable = False else: - _overrides = project_configurations_overrides(project_name) + _project_overrides = project_configurations_overrides(project_name) + _project_anatomy = project_anatomy_overrides(project_name) self.is_overidable = True - overrides = {"project": lib.convert_overrides_to_gui_data(_overrides)} + overrides = {"project": { + PROJECT_CONFIGURATIONS_KEY: lib.convert_overrides_to_gui_data( + _project_overrides + ), + PROJECT_ANATOMY_KEY: lib.convert_overrides_to_gui_data( + _project_anatomy + ) + }} self.project_name = project_name self.ignore_value_changes = True for item in self.input_fields: @@ -481,9 +500,6 @@ class ProjectWidget(QtWidgets.QWidget): # Skip first key all_values = all_values["project"] - prject_defaults_dir = os.path.join( - DEFAULTS_DIR, PROJECT_CONFIGURATIONS_KEY - ) keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: # Skip first key @@ -494,7 +510,7 @@ class ProjectWidget(QtWidgets.QWidget): for key in key_sequence: new_values = new_values[key] - output_path = os.path.join(prject_defaults_dir, subpath) + output_path = os.path.join(DEFAULTS_DIR, subpath) dirpath = os.path.dirname(output_path) if not os.path.exists(dirpath): os.makedirs(dirpath) @@ -533,7 +549,7 @@ class ProjectWidget(QtWidgets.QWidget): return if self.project_name is None: - self._save_defaults() + self._save_studio_overrides() else: self._save_overrides() @@ -548,20 +564,40 @@ class ProjectWidget(QtWidgets.QWidget): data.get("project") or {} ) - overrides_json_path = path_to_project_overrides( + # Saving overrides data + project_overrides_data = output_data.get( + PROJECT_CONFIGURATIONS_KEY, {} + ) + project_overrides_json_path = path_to_project_overrides( self.project_name ) - dirpath = os.path.dirname(overrides_json_path) + dirpath = os.path.dirname(project_overrides_json_path) if not os.path.exists(dirpath): os.makedirs(dirpath) - print("Saving data to:", overrides_json_path) - with open(overrides_json_path, "w") as file_stream: - json.dump(output_data, file_stream, indent=4) + print("Saving data to:", project_overrides_json_path) + with open(project_overrides_json_path, "w") as file_stream: + json.dump(project_overrides_data, file_stream, indent=4) + # Saving anatomy data + project_anatomy_data = output_data.get( + PROJECT_ANATOMY_KEY, {} + ) + project_anatomy_json_path = path_to_project_anatomy( + self.project_name + ) + dirpath = os.path.dirname(project_anatomy_json_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", project_anatomy_json_path) + with open(project_anatomy_json_path, "w") as file_stream: + json.dump(project_anatomy_data, file_stream, indent=4) + + # Refill values with overrides self._on_project_change() - def _save_defaults(self): + def _save_studio_overrides(self): data = {} for input_field in self.input_fields: value, is_group = input_field.studio_overrides() @@ -572,30 +608,44 @@ class ProjectWidget(QtWidgets.QWidget): data.get("project", {}) ) + # Project overrides data + project_overrides_data = output_data.get( + PROJECT_CONFIGURATIONS_KEY, {} + ) dirpath = os.path.dirname(PROJECT_CONFIGURATIONS_PATH) if not os.path.exists(dirpath): os.makedirs(dirpath) print("Saving data to:", PROJECT_CONFIGURATIONS_PATH) - try: - with open(PROJECT_CONFIGURATIONS_PATH, "w") as file_stream: - json.dump(output_data, file_stream, indent=4) - except Exception as exc: - print(output_data) - raise + with open(PROJECT_CONFIGURATIONS_PATH, "w") as file_stream: + json.dump(project_overrides_data, file_stream, indent=4) + # Project Anatomy data + project_anatomy_data = output_data.get( + PROJECT_ANATOMY_KEY, {} + ) + dirpath = os.path.dirname(PROJECT_ANATOMY_PATH) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", PROJECT_ANATOMY_PATH) + with open(PROJECT_ANATOMY_PATH, "w") as file_stream: + json.dump(project_anatomy_data, file_stream, indent=4) + + # Update saved values self._update_values() def _update_values(self): self.ignore_value_changes = True - default_values = { - "project": default_configuration()["project_configurations"] - } + default_values = {"project": default_configuration()} for input_field in self.input_fields: input_field.update_default_values(default_values) - studio_values = {"project": studio_project_configurations()} + studio_values = {"project": { + PROJECT_CONFIGURATIONS_KEY: studio_project_configurations(), + PROJECT_ANATOMY_KEY: studio_project_anatomy() + }} for input_field in self.input_fields: input_field.update_studio_values(studio_values) From 5eb25c7ae3ebdfe7fe66729c2f27a840f6a00dcd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:23:28 +0200 Subject: [PATCH 876/947] overrides are not saved to path from PYPE_CONFIG but to PYPE_PROJECT_CONFIGS --- pype/configurations/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index abd848f95c..9ef782ca6a 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -11,7 +11,7 @@ OVERRIDEN_KEY = "__overriden_keys__" POP_KEY = "__pop_key__" # Folder where studio overrides are stored -STUDIO_OVERRIDES_PATH = os.environ["PYPE_CONFIG"] +STUDIO_OVERRIDES_PATH = os.environ["PYPE_PROJECT_CONFIGS"] # File where studio's system overrides are stored SYSTEM_CONFIGURATIONS_KEY = "system_configurations" From 4d05ebaa71d4cc6a2f0194e610e94e01e07f12fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 17:26:29 +0200 Subject: [PATCH 877/947] fixed roots and templates input data --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index ed73ad6468..b034bc1763 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -227,7 +227,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): super(RootsWidget, self).__init__(parent) self.setObjectName("RootsWidget") - input_data = {"is_group": True} + input_data["is_group"] = True self.initial_attributes(input_data, parent, False) self.key = input_data["key"] @@ -560,7 +560,7 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def __init__(self, input_data, parent): super(TemplatesWidget, self).__init__(parent) - input_data = {"is_group": True} + input_data["is_group"] = True self.initial_attributes(input_data, parent, False) self.key = input_data["key"] From 8a1c164f51efa34dfb5e7a964effdfbea09fcce4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 18:07:53 +0200 Subject: [PATCH 878/947] changed how the margins of main window are --- pype/tools/config_setting/config_setting/widgets/base.py | 4 ++-- pype/tools/config_setting/config_setting/widgets/window.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index 6d25357fc9..a0a470fc97 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -74,7 +74,7 @@ class SystemWidget(QtWidgets.QWidget): footer_layout.addWidget(save_btn, 0) layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(5, 0, 5, 0) + layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) @@ -400,7 +400,7 @@ class ProjectWidget(QtWidgets.QWidget): configurations_widget = QtWidgets.QWidget() configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) - configurations_layout.setContentsMargins(5, 0, 5, 0) + configurations_layout.setContentsMargins(0, 0, 0, 0) configurations_layout.setSpacing(0) configurations_layout.addWidget(scroll_widget, 1) diff --git a/pype/tools/config_setting/config_setting/widgets/window.py b/pype/tools/config_setting/config_setting/widgets/window.py index f8da9a196e..5c7a35fa52 100644 --- a/pype/tools/config_setting/config_setting/widgets/window.py +++ b/pype/tools/config_setting/config_setting/widgets/window.py @@ -8,18 +8,19 @@ class MainWidget(QtWidgets.QWidget): def __init__(self, develop, parent=None): super(MainWidget, self).__init__(parent) + self.setObjectName("MainWidget") self.resize(self.widget_width, self.widget_height) header_tab_widget = QtWidgets.QTabWidget(parent=self) - studio_widget = SystemWidget(develop) - project_widget = ProjectWidget(develop) + studio_widget = SystemWidget(develop, header_tab_widget) + project_widget = ProjectWidget(develop, header_tab_widget) header_tab_widget.addTab(studio_widget, "System") header_tab_widget.addTab(project_widget, "Project") layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) + layout.setContentsMargins(5, 5, 5, 5) layout.setSpacing(0) layout.addWidget(header_tab_widget) From 375188a5587ca6b36719df56af104fefb356855d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 18:08:06 +0200 Subject: [PATCH 879/947] modified tab style --- .../config_setting/style/style.css | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 73c1854bee..5b6821a85d 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -90,7 +90,7 @@ QPushButton[btn-type="expand-toggle"] { } #GroupWidget { - border: 1px solid #aaaaaa; + border-bottom: 1px solid #1d272f; } #ProjectListWidget QListView { @@ -148,6 +148,53 @@ QPushButton[btn-type="expand-toggle"] { #SideLineWidget[state="child-overriden-modified"] {border-color: #106aa2;} #SideLineWidget[state="child-overriden-modified"]:hover {border-color: #137cbd;} +#MainWidget { + background: #141a1f; +} + +QTabWidget::pane { + border-top-style: none; +} + +QTabBar { + background: transparent; +} + +QTabBar::tab { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + padding: 5px; +} + +QTabBar::tab:selected { + background: #293742; + border-color: #9B9B9B; + border-bottom-color: #C2C7CB; +} + +QTabBar::tab:!selected { + margin-top: 2px; + background: #1d272f; +} + +QTabBar::tab:!selected:hover { + background: #3b4f5e; +} + + + +QTabBar::tab:first:selected { + margin-left: 0; +} + +QTabBar::tab:last:selected { + margin-right: 0; +} + +QTabBar::tab:only-one { + margin: 0; +} + QScrollBar:horizontal { height: 15px; margin: 3px 15px 3px 15px; From 529570c906ec6f7465206039e5bf7d47310fb74c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 18:44:57 +0200 Subject: [PATCH 880/947] small changes --- .../defaults/project_anatomy/templates.json | 7 +------ .../project_configurations/plugins/global/publish.json | 3 +-- pype/tools/config_setting/config_setting/style/style.css | 8 ++++---- pype/tools/config_setting/config_setting/widgets/base.py | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/pype/configurations/defaults/project_anatomy/templates.json b/pype/configurations/defaults/project_anatomy/templates.json index 23e9f308f2..0fff0265b3 100644 --- a/pype/configurations/defaults/project_anatomy/templates.json +++ b/pype/configurations/defaults/project_anatomy/templates.json @@ -3,33 +3,28 @@ "version": "v{version:0>{@version_padding}}", "frame_padding": 4, "frame": "{frame:0>{@frame_padding}}", - "work": { "folder": "{root}/{project[name]}/{hierarchy}/{asset}/work/{task}", "file": "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}", "path": "{@folder}/{@file}" }, - "render": { "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/render/{subset}/{@version}", "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", "path": "{@folder}/{@file}" }, - "texture": { "path": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}" }, - "publish": { "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", "path": "{@folder}/{@file}", "thumbnail": "{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}{ext}" }, - "master": { "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/master", "file": "{project[code]}_{asset}_{subset}_master<_{output}><.{frame}>.{representation}", "path": "{@folder}/{@file}" } -} +} \ No newline at end of file diff --git a/pype/configurations/defaults/project_configurations/plugins/global/publish.json b/pype/configurations/defaults/project_configurations/plugins/global/publish.json index d531f1aa47..b946ac4b32 100644 --- a/pype/configurations/defaults/project_configurations/plugins/global/publish.json +++ b/pype/configurations/defaults/project_configurations/plugins/global/publish.json @@ -75,7 +75,6 @@ ] }, "IntegrateAssetNew": { - "enabled": true, "template_name_profiles": { "publish": { "families": [], @@ -96,4 +95,4 @@ "deadline_pool": "", "deadline_group": "" } -} +} \ No newline at end of file diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/config_setting/config_setting/style/style.css index 5b6821a85d..f6dd354935 100644 --- a/pype/tools/config_setting/config_setting/style/style.css +++ b/pype/tools/config_setting/config_setting/style/style.css @@ -44,9 +44,9 @@ QToolButton { QLabel { background: transparent; - color: #808080; + color: #7390a5; } -QLabel:hover {color: #999999;} +QLabel:hover {color: #839caf;} QLabel[state="studio"] {color: #bfccd6;} QLabel[state="studio"]:hover {color: #ffffff;} @@ -122,7 +122,7 @@ QPushButton[btn-type="expand-toggle"] { #SideLineWidget { background-color: #31424e; border-style: solid; - border-color: #808080; + border-color: #3b4f5e; border-left-width: 3px; border-bottom-width: 0px; border-right-width: 0px; @@ -130,7 +130,7 @@ QPushButton[btn-type="expand-toggle"] { } #SideLineWidget:hover { - border-color: #999999; + border-color: #58768d; } #SideLineWidget[state="child-studio"] {border-color: #455c6e;} diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/config_setting/config_setting/widgets/base.py index a0a470fc97..16192aadf3 100644 --- a/pype/tools/config_setting/config_setting/widgets/base.py +++ b/pype/tools/config_setting/config_setting/widgets/base.py @@ -250,7 +250,7 @@ class ProjectListWidget(QtWidgets.QWidget): self.setObjectName("ProjectListWidget") label_widget = QtWidgets.QLabel("Projects") - + label_widget.setProperty("state", "studio") project_list = ProjectListView(self) project_list.setModel(QtGui.QStandardItemModel()) From e9737d9b3a644a1958c66e18dec34101bb647b7d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:01:27 +0200 Subject: [PATCH 881/947] fix default_values for pathwidget --- pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index bc63e27f5a..4a9dd28df1 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2183,10 +2183,10 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): self._had_studio_override = False if not self.multiplatform: - self.input_fields[0].update_studio_values(value) + self.input_fields[0].update_default_values(value) else: for input_field in self.input_fields: - input_field.update_studio_values(value) + input_field.update_default_values(value) def update_studio_values(self, parent_values): self._state = None From 80c091c7fd36dafcc439b15c6e9774322d0418a4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:01:51 +0200 Subject: [PATCH 882/947] project overrides are not stored in subfolder --- pype/configurations/lib.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index 9ef782ca6a..7e24d25483 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -32,11 +32,6 @@ PROJECT_ANATOMY_PATH = os.path.join( STUDIO_OVERRIDES_PATH, PROJECT_ANATOMY_FILENAME ) -# Folder where studio's per project overrides are stored -STUDIO_PROJECT_OVERRIDES_PATH = os.path.join( - STUDIO_OVERRIDES_PATH, "project_overrides" -) - # Path to default configurations DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") @@ -181,7 +176,7 @@ def studio_project_anatomy(): def path_to_project_overrides(project_name): return os.path.join( - STUDIO_PROJECT_OVERRIDES_PATH, + STUDIO_OVERRIDES_PATH, project_name, PROJECT_CONFIGURATIONS_FILENAME ) @@ -189,7 +184,7 @@ def path_to_project_overrides(project_name): def path_to_project_anatomy(project_name): return os.path.join( - STUDIO_PROJECT_OVERRIDES_PATH, + STUDIO_OVERRIDES_PATH, project_name, PROJECT_ANATOMY_FILENAME ) From 098c1bed80f30bcc0dc797b4a43f7e42fa99ff1c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:06:59 +0200 Subject: [PATCH 883/947] fix roots fillings --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index b034bc1763..573f48409d 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -393,9 +393,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.set_multiroot(is_multiroot) if is_multiroot: - self._is_overriden = parent_values is not NOT_SET + self._is_overriden = value is not NOT_SET self._was_overriden = bool(self._is_overriden) - self.multiroot_widget.apply_overrides(parent_values) + self.multiroot_widget.apply_overrides(value) else: self._is_overriden = value is not NOT_SET self._was_overriden = bool(self._is_overriden) From 026358a2ea3b866bda7060a068667fd3711d5f10 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:07:19 +0200 Subject: [PATCH 884/947] change order of filling values logic --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 4a9dd28df1..1280b39904 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -1054,10 +1054,10 @@ class ListWidget(QtWidgets.QWidget, InputObject): # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if value is not None: - if not self._has_studio_override: - item_widget.update_default_values(value) - elif self._is_overriden: + if self._is_overriden: item_widget.apply_overrides(value) + elif not self._has_studio_override: + item_widget.update_default_values(value) else: item_widget.update_studio_values(value) self.hierarchical_style_update() From 35bee377dd6b8c19ee31c62d4ca56510239e08e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:07:39 +0200 Subject: [PATCH 885/947] change logic of roots changes for default, studio and overrides --- .../config_setting/widgets/anatomy_inputs.py | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 573f48409d..ec6da20c72 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -233,7 +233,8 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.key = input_data["key"] self._multiroot_state = None - self.global_is_multiroot = False + self.default_is_multiroot = False + self.studio_is_multiroot = False self.was_multiroot = NOT_SET checkbox_widget = QtWidgets.QWidget(self) @@ -317,7 +318,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): is_multiroot = True break - self.global_is_multiroot = is_multiroot + self.default_is_multiroot = is_multiroot self.was_multiroot = is_multiroot self.set_multiroot(is_multiroot) @@ -346,24 +347,25 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: value = NOT_SET - is_multiroot = False - if isinstance(value, dict): - for _value in value.values(): - if isinstance(_value, dict): - is_multiroot = True - break - - self.global_is_multiroot = is_multiroot - self.was_multiroot = is_multiroot - self.set_multiroot(is_multiroot) - if value is NOT_SET: + is_multiroot = self.default_is_multiroot + self.studio_is_multiroot = NOT_SET self._has_studio_override = False self._had_studio_override = False else: + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + self.studio_is_multiroot = is_multiroot self._has_studio_override = True self._had_studio_override = True + self.was_multiroot = is_multiroot + self.set_multiroot(is_multiroot) + if is_multiroot: self.multiroot_widget.update_studio_values(value) else: @@ -380,7 +382,9 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): value = parent_values.get(self.key, value) if value is NOT_SET: - is_multiroot = self.global_is_multiroot + is_multiroot = self.studio_is_multiroot + if is_multiroot is NOT_SET: + is_multiroot = self.default_is_multiroot else: is_multiroot = False if isinstance(value, dict): @@ -517,7 +521,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._is_overriden = False self._is_modified = False - self.set_multiroot(self.global_is_multiroot) + if self.studio_is_multiroot is NOT_SET: + self.set_multiroot(self.default_is_multiroot) + else: + self.set_multiroot(self.studio_is_multiroot) if self.is_multiroot: self.multiroot_widget.remove_overrides() @@ -530,7 +537,10 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): if self._is_overriden: self.set_multiroot(self.was_multiroot) else: - self.set_multiroot(self.global_is_multiroot) + if self.studio_is_multiroot is NOT_SET: + self.set_multiroot(self.default_is_multiroot) + else: + self.set_multiroot(self.studio_is_multiroot) if self.is_multiroot: self.multiroot_widget.discard_changes() From 3351264c7c1028eac98e665f4747687ff6617e32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:21:28 +0200 Subject: [PATCH 886/947] a little bit better changing from multi to single root --- .../config_setting/widgets/anatomy_inputs.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index ec6da20c72..ed104128a0 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -447,7 +447,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self._state = state def _on_multiroot_checkbox(self): - self.set_multiroot(self.is_multiroot) + self.set_multiroot() def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -471,9 +471,37 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): self.value_changed.emit(self) + def _from_single_to_multi(self): + single_value = self.singleroot_widget.item_value() + mutli_value = self.multiroot_widget.item_value() + first_key = None + for key in mutli_value.keys(): + first_key = key + break + + if first_key is None: + first_key = "" + + mutli_value[first_key] = single_value + + self.multiroot_widget.set_value(mutli_value) + + def _from_multi_to_single(self): + mutli_value = self.multiroot_widget.item_value() + first_key = None + for value in mutli_value.values(): + single_value = value + break + + self.singleroot_widget.set_value(single_value) + def set_multiroot(self, is_multiroot=None): if is_multiroot is None: - is_multiroot = not self.is_multiroot + is_multiroot = self.is_multiroot + if is_multiroot: + self._from_single_to_multi() + else: + self._from_multi_to_single() if is_multiroot != self.is_multiroot: self.multiroot_checkbox.setChecked(is_multiroot) From 93c391a9d4f084b2db79fccb9c13d3f7d331f904 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:27:01 +0200 Subject: [PATCH 887/947] changed labels of actions --- .../config_setting/config_setting/widgets/anatomy_inputs.py | 1 - pype/tools/config_setting/config_setting/widgets/inputs.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index ed104128a0..75ee12ee02 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -488,7 +488,6 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): def _from_multi_to_single(self): mutli_value = self.multiroot_widget.item_value() - first_key = None for value in mutli_value.values(): single_value = value break diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 1280b39904..c7f817a48e 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -201,7 +201,7 @@ class ConfigObject(AbstractConfigObject): and not self.is_overriden and not self.any_parent_is_group ): - action = QtWidgets.QAction("Set as overriden") + action = QtWidgets.QAction("Set project override") actions_mapping[action] = self._set_as_overriden menu.addAction(action) @@ -210,7 +210,7 @@ class ConfigObject(AbstractConfigObject): and (self.is_overriden or self.child_overriden) ): # TODO better label - action = QtWidgets.QAction("Remove override") + action = QtWidgets.QAction("Remove project override") actions_mapping[action] = self._remove_overrides menu.addAction(action) From 17362076a5b56f1e065b81a2029dec33503ab4ef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:37:21 +0200 Subject: [PATCH 888/947] Added possibility to reset to pype's default values --- .../config_setting/widgets/anatomy_inputs.py | 15 +++++ .../config_setting/widgets/inputs.py | 64 +++++++++++++++---- .../config_setting/widgets/widgets.py | 12 ++++ 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py index 75ee12ee02..09544a2455 100644 --- a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py @@ -190,6 +190,10 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): self.root_widget.remove_overrides() self.templates_widget.remove_overrides() + def reset_to_pype_default(self): + self.root_widget.reset_to_pype_default() + self.templates_widget.reset_to_pype_default() + def discard_changes(self): self.root_widget.discard_changes() self.templates_widget.discard_changes() @@ -558,6 +562,14 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): else: self.singleroot_widget.remove_overrides() + def reset_to_pype_default(self): + self.set_multiroot(self.default_is_multiroot) + if self.is_multiroot: + self.multiroot_widget.reset_to_pype_default() + else: + self.singleroot_widget.reset_to_pype_default() + self._has_studio_override = False + def discard_changes(self): self._is_overriden = self._was_overriden self._is_modified = False @@ -703,6 +715,9 @@ class TemplatesWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self.value_input.remove_overrides() + def reset_to_pype_default(self): + self.value_input.reset_to_pype_default() + def discard_changes(self): self.value_input.discard_changes() diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index c7f817a48e..fe2e8032b1 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -176,6 +176,11 @@ class ConfigObject(AbstractConfigObject): self.discard_changes() self.ignore_value_changes = False + def _reset_to_pype_default(self): + self.ignore_value_changes = True + self.reset_to_pype_default() + self.ignore_value_changes = False + def _remove_overrides(self): self.ignore_value_changes = True self.remove_overrides() @@ -205,6 +210,17 @@ class ConfigObject(AbstractConfigObject): actions_mapping[action] = self._set_as_overriden menu.addAction(action) + if ( + not self.is_overidable + and ( + self.has_studio_override + or self.child_has_studio_override + ) + ): + action = QtWidgets.QAction("Reset to pype default") + actions_mapping[action] = self._reset_to_pype_default + menu.addAction(action) + if ( not self.any_parent_overriden() and (self.is_overriden or self.child_overriden) @@ -361,6 +377,10 @@ class InputObject(ConfigObject): self._is_overriden = False self._is_modified = False + def reset_to_pype_default(self): + self.set_value(self.default_value) + self._has_studio_override = False + def discard_changes(self): self._is_overriden = self._was_overriden self._has_studio_override = self._had_studio_override @@ -1618,15 +1638,20 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - for item in self.input_fields: - item.remove_overrides() + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False def discard_changes(self): self._is_overriden = self._was_overriden self._is_modified = False - for item in self.input_fields: - item.discard_changes() + for input_field in self.input_fields: + input_field.discard_changes() self._is_modified = self.child_modified @@ -1943,15 +1968,20 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - for item in self.input_fields: - item.remove_overrides() + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False def discard_changes(self): self._is_modified = False self._is_overriden = self._was_overriden - for item in self.input_fields: - item.discard_changes() + for input_field in self.input_fields: + input_field.discard_changes() self._is_modified = self.child_modified @@ -2309,8 +2339,13 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - for item in self.input_fields: - item.remove_overrides() + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False def discard_changes(self): self._is_modified = False @@ -2467,8 +2502,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - for item in self.input_fields: - item.remove_overrides() + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False def set_as_overriden(self): if self.is_overriden: diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index d7631e6fea..e8286fd919 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -400,6 +400,18 @@ class AbstractConfigObject: ) ) + def _reset_to_pype_default(self): + self.ignore_value_changes = True + self.reset_to_pype_default() + self.ignore_value_changes = False + + def reset_to_pype_default(self): + raise NotImplementedError( + "{} Method `reset_to_pype_default` not implemented!".format( + repr(self) + ) + ) + def _remove_overrides(self): self.ignore_value_changes = True self.remove_overrides() From 4996c5617041f872d487c67c38700886d3e5ad2a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:38:32 +0200 Subject: [PATCH 889/947] removed duplicated methods --- .../config_setting/widgets/inputs.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index fe2e8032b1..55b0c999d0 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -171,25 +171,6 @@ class ConfigObject(AbstractConfigObject): return "-".join(items) or cls.default_state - def _discard_changes(self): - self.ignore_value_changes = True - self.discard_changes() - self.ignore_value_changes = False - - def _reset_to_pype_default(self): - self.ignore_value_changes = True - self.reset_to_pype_default() - self.ignore_value_changes = False - - def _remove_overrides(self): - self.ignore_value_changes = True - self.remove_overrides() - self.ignore_value_changes = False - - def _set_as_overriden(self): - self.ignore_value_changes = True - self.set_as_overriden() - self.ignore_value_changes = False def mouseReleaseEvent(self, event): if self.allow_actions and event.button() == QtCore.Qt.RightButton: From 57ac12b5140def9432d16de21fc82b28a048b3fd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 11 Sep 2020 19:43:44 +0200 Subject: [PATCH 890/947] added set studio defaults action --- .../config_setting/widgets/inputs.py | 43 ++++++++++++++++++- .../config_setting/widgets/widgets.py | 12 ++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 55b0c999d0..fba799e5c0 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -171,7 +171,6 @@ class ConfigObject(AbstractConfigObject): return "-".join(items) or cls.default_state - def mouseReleaseEvent(self, event): if self.allow_actions and event.button() == QtCore.Qt.RightButton: menu = QtWidgets.QMenu() @@ -202,6 +201,17 @@ class ConfigObject(AbstractConfigObject): actions_mapping[action] = self._reset_to_pype_default menu.addAction(action) + if ( + not self.is_overidable + and ( + (self.is_group and not self._had_studio_override) + or self.any_parent_is_group + ) + ): + action = QtWidgets.QAction("Set sudio default") + actions_mapping[action] = self._set_studio_default + menu.addAction(action) + if ( not self.any_parent_overriden() and (self.is_overriden or self.child_overriden) @@ -362,6 +372,9 @@ class InputObject(ConfigObject): self.set_value(self.default_value) self._has_studio_override = False + def set_studio_default(self): + self._has_studio_override = True + def discard_changes(self): self._is_overriden = self._was_overriden self._has_studio_override = self._had_studio_override @@ -1627,6 +1640,13 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): input_field.reset_to_pype_default() self._has_studio_override = False + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + def discard_changes(self): self._is_overriden = self._was_overriden self._is_modified = False @@ -1957,6 +1977,13 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): input_field.reset_to_pype_default() self._has_studio_override = False + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + def discard_changes(self): self._is_modified = False self._is_overriden = self._was_overriden @@ -2328,6 +2355,13 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): input_field.reset_to_pype_default() self._has_studio_override = False + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + def discard_changes(self): self._is_modified = False self._is_overriden = self._was_overriden @@ -2491,6 +2525,13 @@ class DictFormWidget(QtWidgets.QWidget, ConfigObject): input_field.reset_to_pype_default() self._has_studio_override = False + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + def set_as_overriden(self): if self.is_overriden: return diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/config_setting/config_setting/widgets/widgets.py index e8286fd919..aa1f17a7f4 100644 --- a/pype/tools/config_setting/config_setting/widgets/widgets.py +++ b/pype/tools/config_setting/config_setting/widgets/widgets.py @@ -400,6 +400,18 @@ class AbstractConfigObject: ) ) + def _set_studio_default(self): + self.ignore_value_changes = True + self.set_studio_default() + self.ignore_value_changes = False + + def set_studio_default(self): + raise NotImplementedError( + "{} Method `set_studio_default` not implemented!".format( + repr(self) + ) + ) + def _reset_to_pype_default(self): self.ignore_value_changes = True self.reset_to_pype_default() From 689438f63adfa1f264071c9e9f266a733a272ba6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 10:11:09 +0200 Subject: [PATCH 891/947] removed separators from PathWidget --- pype/tools/config_setting/config_setting/widgets/inputs.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index fba799e5c0..61a5aac435 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -2089,12 +2089,6 @@ class PathWidget(QtWidgets.QWidget, ConfigObject): "darwin": "MacOS", "linux": "Linux" } - # TODO be able to save and load with separators - platform_separators = { - "windows": ";", - "darwin": ":", - "linux": ":" - } def __init__( self, input_data, parent, From ffa4bd4f073997f3092750f6259a35f68c05f2a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 10:32:20 +0200 Subject: [PATCH 892/947] ListWidget has items next to label instead of under --- .../config_setting/config_setting/widgets/inputs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/config_setting/config_setting/widgets/inputs.py index 61a5aac435..fa809cd2d2 100644 --- a/pype/tools/config_setting/config_setting/widgets/inputs.py +++ b/pype/tools/config_setting/config_setting/widgets/inputs.py @@ -999,14 +999,14 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.input_fields = [] - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 5) + layout.setSpacing(5) if not label_widget: - label = input_data["label"] - label_widget = QtWidgets.QLabel(label) + label_widget = QtWidgets.QLabel(input_data["label"], self) layout.addWidget(label_widget) + self.label_widget = label_widget inputs_widget = QtWidgets.QWidget(self) @@ -1014,7 +1014,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): layout.addWidget(inputs_widget) inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) - inputs_layout.setContentsMargins(0, 5, 0, 5) + inputs_layout.setContentsMargins(0, 0, 0, 0) inputs_layout.setSpacing(3) self.inputs_widget = inputs_widget From dff5da3a5ef2e3f2c0d7a9f5b684be8c536b899b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 11:42:33 +0200 Subject: [PATCH 893/947] removed label from dict-invisible --- .../config_gui_schema/system_schema/0_system_gui_schema.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json index bdc0158511..b16545111c 100644 --- a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json +++ b/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json @@ -5,7 +5,6 @@ { "type": "dict-invisible", "key": "global", - "label": "Global", "children": [{ "type": "schema", "children": [ From 8f10391b53ae23640c9ffcb016419a57ffa49320 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 12:21:55 +0200 Subject: [PATCH 894/947] renamed config_setting to settings --- pype/tools/helpguide.txt | 153 ++++++++++++++++++ .../{config_setting => settings}/__init__.py | 2 +- .../{config_setting => settings}/__main__.py | 8 +- .../settings}/__init__.py | 0 .../projects_schema/0_project_gui_schema.json | 0 .../projects_schema/1_plugins_gui_schema.json | 0 .../system_schema/0_system_gui_schema.json | 0 .../1_applications_gui_schema.json | 0 .../system_schema/1_intents_gui_schema.json | 0 .../system_schema/1_tools_gui_schema.json | 0 .../system_schema/1_tray_items.json | 0 .../settings}/style/__init__.py | 0 .../settings}/style/pype_icon.png | Bin .../settings}/style/style.css | 0 .../settings}/widgets/__init__.py | 0 .../settings}/widgets/anatomy_inputs.py | 0 .../settings}/widgets/base.py | 0 .../settings}/widgets/inputs.py | 0 .../settings}/widgets/lib.py | 0 .../settings}/widgets/tests.py | 0 .../settings}/widgets/widgets.py | 0 .../settings}/widgets/window.py | 0 22 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 pype/tools/helpguide.txt rename pype/tools/{config_setting => settings}/__init__.py (50%) rename pype/tools/{config_setting => settings}/__main__.py (53%) rename pype/tools/{config_setting/config_setting => settings/settings}/__init__.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/projects_schema/0_project_gui_schema.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/projects_schema/1_plugins_gui_schema.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/system_schema/0_system_gui_schema.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/system_schema/1_applications_gui_schema.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/system_schema/1_intents_gui_schema.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/system_schema/1_tools_gui_schema.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/config_gui_schema/system_schema/1_tray_items.json (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/style/__init__.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/style/pype_icon.png (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/style/style.css (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/__init__.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/anatomy_inputs.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/base.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/inputs.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/lib.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/tests.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/widgets.py (100%) rename pype/tools/{config_setting/config_setting => settings/settings}/widgets/window.py (100%) diff --git a/pype/tools/helpguide.txt b/pype/tools/helpguide.txt new file mode 100644 index 0000000000..b0c9471e3e --- /dev/null +++ b/pype/tools/helpguide.txt @@ -0,0 +1,153 @@ +## Basic rules +- configurations does not define GUI, but GUI defines configurations! +- output is always json (yaml is not needed for anatomy templates anymore) +- GUI schema has multiple input types, all inputs are represented by a dictionary +- each input may have "input modifiers" (keys in dictionary) that are required or optional + - only required modifier for all input items is key `"type"` which says what type of item it is +- there are special keys across all inputs + - `"is_file"` - this key is for storing pype defaults in `pype` repo + - reasons of existence: developing new schemas does not require to create defaults manually + - key is validated, must be once in hierarchy else it won't be possible to store pype defaults + - `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides + - key is validated, can be only once in hierarchy but is not required + +## Basic Dictionary inputs +- these inputs wraps another inputs into {key: value} relation + +## dict-invisible +- this input gives ability to wrap another inputs but keep them in same widget without visible divider + - this is used as first input widget +- has required keys `"key"` and `"children"` + - "children" says what children inputs are underneath + - "key" is key under which will be stored value from it's children +- output is dictionary `{the "key": children values}` + +``` +{ + "type": "dict-invisible", + "key": "global", + "children": [{ + ... + }] +} +``` + +## dict +- this is another dictionary input wrapping more inputs but visually makes them different + +``` +{ + "key": "applications", + "type": "dict", + "label": "Applications", + "expandable": true, + "is_group": true, + "is_file": true, +} +``` + +## Inputs for setting any kind of value (`Pure` inputs) +- all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input + - unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them + +### boolean +- simple checkbox, nothing more to set +``` +{ + "type": "boolean", + "key": "my_boolean_key", + "label": "Do you want to use Pype?" +} +``` + +### number +- number input, can be used for both integer and float + - key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`) + - key `"minimum"` as minimum allowed number to enter (Default: `-99999`) + - key `"maxium"` as maximum allowed number to enter (Default: `99999`) +``` +{ + "type": "number", + "key": "fps", + "label": "Frame rate (FPS)" + "decimal": 2, + "minimum": 1, + "maximum": 300000 +} +``` + +### text +- simple text input + - key `"multiline"` allows to enter multiple lines of text (Default: `False`) + +``` +{ + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" +} +``` + +### raw-json +- a little bit enhanced text input for raw json +- has validations of json format + - empty value is invalid value, always must be at least `{}` of `[]` + +``` +{ + "type": "raw-json", + "key": "profiles", + "label": "Extract Review profiles" +} +``` + +## Inputs for setting value using Pure inputs +- these inputs also have required `"key"` and `"label"` +- they use Pure inputs "as widgets" + +### list +- output is list +- items can be added and removed +- items in list must be the same type + - type of items is defined with key `"object_type"` where Pure input name is entered (e.g. `number`) + - because Pure inputs may have modifiers (`number` input has `minimum`, `maximum` and `decimals`) you can set these in key `"input_modifiers"` + +``` +{ + "type": "list", + "object_type": "number", + "key": "exclude_ports", + "label": "Exclude ports", + "input_modifiers": { + "minimum": 1, + "maximum": 65535 + } +} +``` + +### dict-modifiable +- one of dictionary inputs, this is only used as value input +- items in this input can be removed and added same way as in `list` input +- value items in dictionary must be the same type + - type of items is defined with key `"object_type"` where Pure input name is entered (e.g. `number`) + - because Pure inputs may have modifiers (`number` input has `minimum`, `maximum` and `decimals`) you can set these in key `"input_modifiers"` +- this input can be expandable + - that can be set with key `"expandable"` as `True`/`False` (Default: `True`) + - with key `"expanded"` as `True`/`False` can be set that is expanded when GUI is opened (Default: `False`) + +``` +{ + "type": "dict-modifiable", + "object_type": "number", + "input_modifiers": { + "minimum": 0, + "maximum": 300 + }, + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true +} +``` + +Currently there are `system configurations` and `project configurations`. Both has `root` schema where all begins. diff --git a/pype/tools/config_setting/__init__.py b/pype/tools/settings/__init__.py similarity index 50% rename from pype/tools/config_setting/__init__.py rename to pype/tools/settings/__init__.py index c3bd49449d..7df121f06e 100644 --- a/pype/tools/config_setting/__init__.py +++ b/pype/tools/settings/__init__.py @@ -1,4 +1,4 @@ -from config_setting import style, MainWidget +from settings import style, MainWidget __all__ = ( diff --git a/pype/tools/config_setting/__main__.py b/pype/tools/settings/__main__.py similarity index 53% rename from pype/tools/config_setting/__main__.py rename to pype/tools/settings/__main__.py index 0e4ab1c0aa..044c2ef495 100644 --- a/pype/tools/config_setting/__main__.py +++ b/pype/tools/settings/__main__.py @@ -1,18 +1,18 @@ import sys -import config_setting +import settings from Qt import QtWidgets, QtGui if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) - stylesheet = config_setting.style.load_stylesheet() + stylesheet = settings.style.load_stylesheet() app.setStyleSheet(stylesheet) - app.setWindowIcon(QtGui.QIcon(config_setting.style.app_icon_path())) + app.setWindowIcon(QtGui.QIcon(settings.style.app_icon_path())) develop = "-dev" in sys.argv - widget = config_setting.MainWidget(develop) + widget = settings.MainWidget(develop) widget.show() sys.exit(app.exec_()) diff --git a/pype/tools/config_setting/config_setting/__init__.py b/pype/tools/settings/settings/__init__.py similarity index 100% rename from pype/tools/config_setting/config_setting/__init__.py rename to pype/tools/settings/settings/__init__.py diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/settings/settings/config_gui_schema/projects_schema/0_project_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/0_project_gui_schema.json rename to pype/tools/settings/settings/config_gui_schema/projects_schema/0_project_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/config_gui_schema/projects_schema/1_plugins_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/projects_schema/1_plugins_gui_schema.json rename to pype/tools/settings/settings/config_gui_schema/projects_schema/1_plugins_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json b/pype/tools/settings/settings/config_gui_schema/system_schema/0_system_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/system_schema/0_system_gui_schema.json rename to pype/tools/settings/settings/config_gui_schema/system_schema/0_system_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json b/pype/tools/settings/settings/config_gui_schema/system_schema/1_applications_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_applications_gui_schema.json rename to pype/tools/settings/settings/config_gui_schema/system_schema/1_applications_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json b/pype/tools/settings/settings/config_gui_schema/system_schema/1_intents_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_intents_gui_schema.json rename to pype/tools/settings/settings/config_gui_schema/system_schema/1_intents_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json b/pype/tools/settings/settings/config_gui_schema/system_schema/1_tools_gui_schema.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tools_gui_schema.json rename to pype/tools/settings/settings/config_gui_schema/system_schema/1_tools_gui_schema.json diff --git a/pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json b/pype/tools/settings/settings/config_gui_schema/system_schema/1_tray_items.json similarity index 100% rename from pype/tools/config_setting/config_setting/config_gui_schema/system_schema/1_tray_items.json rename to pype/tools/settings/settings/config_gui_schema/system_schema/1_tray_items.json diff --git a/pype/tools/config_setting/config_setting/style/__init__.py b/pype/tools/settings/settings/style/__init__.py similarity index 100% rename from pype/tools/config_setting/config_setting/style/__init__.py rename to pype/tools/settings/settings/style/__init__.py diff --git a/pype/tools/config_setting/config_setting/style/pype_icon.png b/pype/tools/settings/settings/style/pype_icon.png similarity index 100% rename from pype/tools/config_setting/config_setting/style/pype_icon.png rename to pype/tools/settings/settings/style/pype_icon.png diff --git a/pype/tools/config_setting/config_setting/style/style.css b/pype/tools/settings/settings/style/style.css similarity index 100% rename from pype/tools/config_setting/config_setting/style/style.css rename to pype/tools/settings/settings/style/style.css diff --git a/pype/tools/config_setting/config_setting/widgets/__init__.py b/pype/tools/settings/settings/widgets/__init__.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/__init__.py rename to pype/tools/settings/settings/widgets/__init__.py diff --git a/pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py b/pype/tools/settings/settings/widgets/anatomy_inputs.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/anatomy_inputs.py rename to pype/tools/settings/settings/widgets/anatomy_inputs.py diff --git a/pype/tools/config_setting/config_setting/widgets/base.py b/pype/tools/settings/settings/widgets/base.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/base.py rename to pype/tools/settings/settings/widgets/base.py diff --git a/pype/tools/config_setting/config_setting/widgets/inputs.py b/pype/tools/settings/settings/widgets/inputs.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/inputs.py rename to pype/tools/settings/settings/widgets/inputs.py diff --git a/pype/tools/config_setting/config_setting/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/lib.py rename to pype/tools/settings/settings/widgets/lib.py diff --git a/pype/tools/config_setting/config_setting/widgets/tests.py b/pype/tools/settings/settings/widgets/tests.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/tests.py rename to pype/tools/settings/settings/widgets/tests.py diff --git a/pype/tools/config_setting/config_setting/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/widgets.py rename to pype/tools/settings/settings/widgets/widgets.py diff --git a/pype/tools/config_setting/config_setting/widgets/window.py b/pype/tools/settings/settings/widgets/window.py similarity index 100% rename from pype/tools/config_setting/config_setting/widgets/window.py rename to pype/tools/settings/settings/widgets/window.py From 9e016906458aeaa15634f9f9e1ca03f843e1a4ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 12:27:15 +0200 Subject: [PATCH 895/947] folder config_gui_schema renamed to gui_schemas --- .../projects_schema/0_project_gui_schema.json | 0 .../projects_schema/1_plugins_gui_schema.json | 0 .../system_schema/0_system_gui_schema.json | 0 .../system_schema/1_applications_gui_schema.json | 0 .../system_schema/1_intents_gui_schema.json | 0 .../system_schema/1_tools_gui_schema.json | 0 .../system_schema/1_tray_items.json | 0 pype/tools/settings/settings/widgets/lib.py | 2 +- 8 files changed, 1 insertion(+), 1 deletion(-) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/projects_schema/0_project_gui_schema.json (100%) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/projects_schema/1_plugins_gui_schema.json (100%) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/system_schema/0_system_gui_schema.json (100%) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/system_schema/1_applications_gui_schema.json (100%) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/system_schema/1_intents_gui_schema.json (100%) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/system_schema/1_tools_gui_schema.json (100%) rename pype/tools/settings/settings/{config_gui_schema => gui_schemas}/system_schema/1_tray_items.json (100%) diff --git a/pype/tools/settings/settings/config_gui_schema/projects_schema/0_project_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/projects_schema/0_project_gui_schema.json rename to pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json diff --git a/pype/tools/settings/settings/config_gui_schema/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/projects_schema/1_plugins_gui_schema.json rename to pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json diff --git a/pype/tools/settings/settings/config_gui_schema/system_schema/0_system_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/system_schema/0_system_gui_schema.json rename to pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json diff --git a/pype/tools/settings/settings/config_gui_schema/system_schema/1_applications_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/system_schema/1_applications_gui_schema.json rename to pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json diff --git a/pype/tools/settings/settings/config_gui_schema/system_schema/1_intents_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/system_schema/1_intents_gui_schema.json rename to pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json diff --git a/pype/tools/settings/settings/config_gui_schema/system_schema/1_tools_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/system_schema/1_tools_gui_schema.json rename to pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json diff --git a/pype/tools/settings/settings/config_gui_schema/system_schema/1_tray_items.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json similarity index 100% rename from pype/tools/settings/settings/config_gui_schema/system_schema/1_tray_items.json rename to pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index daba87de15..9f54b090cb 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -278,7 +278,7 @@ def gui_schema(subfolder, main_schema_name): subfolder, main_schema_name dirpath = os.path.join( os.path.dirname(os.path.dirname(__file__)), - "config_gui_schema", + "gui_schemas", subfolder ) From 1c8d2c3a3065da76e13d2c65480f320faf6645d5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 12:53:20 +0200 Subject: [PATCH 896/947] removed temp file --- pype/tools/helpguide.txt | 153 --------------------------------------- 1 file changed, 153 deletions(-) delete mode 100644 pype/tools/helpguide.txt diff --git a/pype/tools/helpguide.txt b/pype/tools/helpguide.txt deleted file mode 100644 index b0c9471e3e..0000000000 --- a/pype/tools/helpguide.txt +++ /dev/null @@ -1,153 +0,0 @@ -## Basic rules -- configurations does not define GUI, but GUI defines configurations! -- output is always json (yaml is not needed for anatomy templates anymore) -- GUI schema has multiple input types, all inputs are represented by a dictionary -- each input may have "input modifiers" (keys in dictionary) that are required or optional - - only required modifier for all input items is key `"type"` which says what type of item it is -- there are special keys across all inputs - - `"is_file"` - this key is for storing pype defaults in `pype` repo - - reasons of existence: developing new schemas does not require to create defaults manually - - key is validated, must be once in hierarchy else it won't be possible to store pype defaults - - `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides - - key is validated, can be only once in hierarchy but is not required - -## Basic Dictionary inputs -- these inputs wraps another inputs into {key: value} relation - -## dict-invisible -- this input gives ability to wrap another inputs but keep them in same widget without visible divider - - this is used as first input widget -- has required keys `"key"` and `"children"` - - "children" says what children inputs are underneath - - "key" is key under which will be stored value from it's children -- output is dictionary `{the "key": children values}` - -``` -{ - "type": "dict-invisible", - "key": "global", - "children": [{ - ... - }] -} -``` - -## dict -- this is another dictionary input wrapping more inputs but visually makes them different - -``` -{ - "key": "applications", - "type": "dict", - "label": "Applications", - "expandable": true, - "is_group": true, - "is_file": true, -} -``` - -## Inputs for setting any kind of value (`Pure` inputs) -- all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input - - unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them - -### boolean -- simple checkbox, nothing more to set -``` -{ - "type": "boolean", - "key": "my_boolean_key", - "label": "Do you want to use Pype?" -} -``` - -### number -- number input, can be used for both integer and float - - key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`) - - key `"minimum"` as minimum allowed number to enter (Default: `-99999`) - - key `"maxium"` as maximum allowed number to enter (Default: `99999`) -``` -{ - "type": "number", - "key": "fps", - "label": "Frame rate (FPS)" - "decimal": 2, - "minimum": 1, - "maximum": 300000 -} -``` - -### text -- simple text input - - key `"multiline"` allows to enter multiple lines of text (Default: `False`) - -``` -{ - "type": "text", - "key": "deadline_pool", - "label": "Deadline pool" -} -``` - -### raw-json -- a little bit enhanced text input for raw json -- has validations of json format - - empty value is invalid value, always must be at least `{}` of `[]` - -``` -{ - "type": "raw-json", - "key": "profiles", - "label": "Extract Review profiles" -} -``` - -## Inputs for setting value using Pure inputs -- these inputs also have required `"key"` and `"label"` -- they use Pure inputs "as widgets" - -### list -- output is list -- items can be added and removed -- items in list must be the same type - - type of items is defined with key `"object_type"` where Pure input name is entered (e.g. `number`) - - because Pure inputs may have modifiers (`number` input has `minimum`, `maximum` and `decimals`) you can set these in key `"input_modifiers"` - -``` -{ - "type": "list", - "object_type": "number", - "key": "exclude_ports", - "label": "Exclude ports", - "input_modifiers": { - "minimum": 1, - "maximum": 65535 - } -} -``` - -### dict-modifiable -- one of dictionary inputs, this is only used as value input -- items in this input can be removed and added same way as in `list` input -- value items in dictionary must be the same type - - type of items is defined with key `"object_type"` where Pure input name is entered (e.g. `number`) - - because Pure inputs may have modifiers (`number` input has `minimum`, `maximum` and `decimals`) you can set these in key `"input_modifiers"` -- this input can be expandable - - that can be set with key `"expandable"` as `True`/`False` (Default: `True`) - - with key `"expanded"` as `True`/`False` can be set that is expanded when GUI is opened (Default: `False`) - -``` -{ - "type": "dict-modifiable", - "object_type": "number", - "input_modifiers": { - "minimum": 0, - "maximum": 300 - }, - "is_group": true, - "key": "templates_mapping", - "label": "Muster - Templates mapping", - "is_file": true -} -``` - -Currently there are `system configurations` and `project configurations`. Both has `root` schema where all begins. From 2f2eb3689bf726cf5ecc4613ad6eafe0e1309d9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:13:43 +0200 Subject: [PATCH 897/947] extracted config.py to __init__.py --- pype/configurations/__init__.py | 9 +++++++++ pype/configurations/config.py | 24 ------------------------ pype/configurations/lib.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 pype/configurations/__init__.py delete mode 100644 pype/configurations/config.py diff --git a/pype/configurations/__init__.py b/pype/configurations/__init__.py new file mode 100644 index 0000000000..e3fc53fcfd --- /dev/null +++ b/pype/configurations/__init__.py @@ -0,0 +1,9 @@ +from .lib import ( + system_configurations, + project_configurations +) + +__all__ = ( + "system_configurations", + "project_configurations" +) diff --git a/pype/configurations/config.py b/pype/configurations/config.py deleted file mode 100644 index ed7104427f..0000000000 --- a/pype/configurations/config.py +++ /dev/null @@ -1,24 +0,0 @@ -from .lib import ( - apply_overrides, - default_configuration, - studio_system_configurations, - studio_project_configurations, - project_configurations_overrides -) - - -def system_configurations(): - default_values = default_configuration()["system_configurations"] - studio_values = studio_system_configurations() - return apply_overrides(default_values, studio_values) - - -def project_configurations(project_name): - default_values = default_configuration()["project_configurations"] - studio_values = studio_project_configurations() - - studio_overrides = apply_overrides(default_values, studio_values) - - project_overrides = project_configurations_overrides(project_name) - - return apply_overrides(studio_overrides, project_overrides) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index 7e24d25483..6fb90261e1 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -239,3 +239,20 @@ def apply_overrides(global_presets, project_overrides): if not project_overrides: return global_presets return merge_overrides(global_presets, project_overrides) + + +def system_configurations(): + default_values = default_configuration()["system_configurations"] + studio_values = studio_system_configurations() + return apply_overrides(default_values, studio_values) + + +def project_configurations(project_name): + default_values = default_configuration()["project_configurations"] + studio_values = studio_project_configurations() + + studio_overrides = apply_overrides(default_values, studio_values) + + project_overrides = project_configurations_overrides(project_name) + + return apply_overrides(studio_overrides, project_overrides) From 9b466e93728e324462961cfb210bb059c5b4c279 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:15:40 +0200 Subject: [PATCH 898/947] renamed configuration constants to settings constant names --- pype/configurations/lib.py | 38 ++++++++++---------- pype/tools/settings/settings/widgets/base.py | 26 +++++++------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pype/configurations/lib.py b/pype/configurations/lib.py index 6fb90261e1..101b579b14 100644 --- a/pype/configurations/lib.py +++ b/pype/configurations/lib.py @@ -14,16 +14,16 @@ POP_KEY = "__pop_key__" STUDIO_OVERRIDES_PATH = os.environ["PYPE_PROJECT_CONFIGS"] # File where studio's system overrides are stored -SYSTEM_CONFIGURATIONS_KEY = "system_configurations" -SYSTEM_CONFIGURATIONS_PATH = os.path.join( - STUDIO_OVERRIDES_PATH, SYSTEM_CONFIGURATIONS_KEY + ".json" +SYSTEM_SETTINGS_KEY = "system_configurations" +SYSTEM_SETTINGS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, SYSTEM_SETTINGS_KEY + ".json" ) # File where studio's default project overrides are stored -PROJECT_CONFIGURATIONS_KEY = "project_configurations" -PROJECT_CONFIGURATIONS_FILENAME = PROJECT_CONFIGURATIONS_KEY + ".json" -PROJECT_CONFIGURATIONS_PATH = os.path.join( - STUDIO_OVERRIDES_PATH, PROJECT_CONFIGURATIONS_FILENAME +PROJECT_SETTINGS_KEY = "project_configurations" +PROJECT_SETTINGS_FILENAME = PROJECT_SETTINGS_KEY + ".json" +PROJECT_SETTINGS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, PROJECT_SETTINGS_FILENAME ) PROJECT_ANATOMY_KEY = "project_anatomy" @@ -36,19 +36,19 @@ PROJECT_ANATOMY_PATH = os.path.join( DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") # Variable where cache of default configurations are stored -_DEFAULT_CONFIGURATIONS = None +_DEFAULT_SETTINGS = None def reset_default_configurations(): - global _DEFAULT_CONFIGURATIONS - _DEFAULT_CONFIGURATIONS = None + global _DEFAULT_SETTINGS + _DEFAULT_SETTINGS = None def default_configuration(): - global _DEFAULT_CONFIGURATIONS - if _DEFAULT_CONFIGURATIONS is None: - _DEFAULT_CONFIGURATIONS = load_jsons_from_dir(DEFAULTS_DIR) - return _DEFAULT_CONFIGURATIONS + global _DEFAULT_SETTINGS + if _DEFAULT_SETTINGS is None: + _DEFAULT_SETTINGS = load_jsons_from_dir(DEFAULTS_DIR) + return _DEFAULT_SETTINGS def load_json(fpath): @@ -157,14 +157,14 @@ def load_jsons_from_dir(path, *args, **kwargs): def studio_system_configurations(): - if os.path.exists(SYSTEM_CONFIGURATIONS_PATH): - return load_json(SYSTEM_CONFIGURATIONS_PATH) + if os.path.exists(SYSTEM_SETTINGS_PATH): + return load_json(SYSTEM_SETTINGS_PATH) return {} def studio_project_configurations(): - if os.path.exists(PROJECT_CONFIGURATIONS_PATH): - return load_json(PROJECT_CONFIGURATIONS_PATH) + if os.path.exists(PROJECT_SETTINGS_PATH): + return load_json(PROJECT_SETTINGS_PATH) return {} @@ -178,7 +178,7 @@ def path_to_project_overrides(project_name): return os.path.join( STUDIO_OVERRIDES_PATH, project_name, - PROJECT_CONFIGURATIONS_FILENAME + PROJECT_SETTINGS_FILENAME ) diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 16192aadf3..e1d167a9f2 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -3,9 +3,9 @@ import json from Qt import QtWidgets, QtCore, QtGui from pype.configurations.lib import ( SYSTEM_CONFIGURATIONS_KEY, - SYSTEM_CONFIGURATIONS_PATH, - PROJECT_CONFIGURATIONS_KEY, - PROJECT_CONFIGURATIONS_PATH, + SYSTEM_SETTINGS_PATH, + PROJECT_SETTINGS_KEY, + PROJECT_SETTINGS_PATH, PROJECT_ANATOMY_KEY, PROJECT_ANATOMY_PATH, @@ -148,12 +148,12 @@ class SystemWidget(QtWidgets.QWidget): values = lib.convert_gui_data_to_overrides(_data.get("system", {})) - dirpath = os.path.dirname(SYSTEM_CONFIGURATIONS_PATH) + dirpath = os.path.dirname(SYSTEM_SETTINGS_PATH) if not os.path.exists(dirpath): os.makedirs(dirpath) - print("Saving data to:", SYSTEM_CONFIGURATIONS_PATH) - with open(SYSTEM_CONFIGURATIONS_PATH, "w") as file_stream: + print("Saving data to:", SYSTEM_SETTINGS_PATH) + with open(SYSTEM_SETTINGS_PATH, "w") as file_stream: json.dump(values, file_stream, indent=4) self._update_values() @@ -467,7 +467,7 @@ class ProjectWidget(QtWidgets.QWidget): self.is_overidable = True overrides = {"project": { - PROJECT_CONFIGURATIONS_KEY: lib.convert_overrides_to_gui_data( + PROJECT_SETTINGS_KEY: lib.convert_overrides_to_gui_data( _project_overrides ), PROJECT_ANATOMY_KEY: lib.convert_overrides_to_gui_data( @@ -566,7 +566,7 @@ class ProjectWidget(QtWidgets.QWidget): # Saving overrides data project_overrides_data = output_data.get( - PROJECT_CONFIGURATIONS_KEY, {} + PROJECT_SETTINGS_KEY, {} ) project_overrides_json_path = path_to_project_overrides( self.project_name @@ -610,14 +610,14 @@ class ProjectWidget(QtWidgets.QWidget): # Project overrides data project_overrides_data = output_data.get( - PROJECT_CONFIGURATIONS_KEY, {} + PROJECT_SETTINGS_KEY, {} ) - dirpath = os.path.dirname(PROJECT_CONFIGURATIONS_PATH) + dirpath = os.path.dirname(PROJECT_SETTINGS_PATH) if not os.path.exists(dirpath): os.makedirs(dirpath) - print("Saving data to:", PROJECT_CONFIGURATIONS_PATH) - with open(PROJECT_CONFIGURATIONS_PATH, "w") as file_stream: + print("Saving data to:", PROJECT_SETTINGS_PATH) + with open(PROJECT_SETTINGS_PATH, "w") as file_stream: json.dump(project_overrides_data, file_stream, indent=4) # Project Anatomy data @@ -643,7 +643,7 @@ class ProjectWidget(QtWidgets.QWidget): input_field.update_default_values(default_values) studio_values = {"project": { - PROJECT_CONFIGURATIONS_KEY: studio_project_configurations(), + PROJECT_SETTINGS_KEY: studio_project_configurations(), PROJECT_ANATOMY_KEY: studio_project_anatomy() }} for input_field in self.input_fields: From b4860169c58fb8f3a964bb3915668f99fc3d31dc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:17:50 +0200 Subject: [PATCH 899/947] renamed most of variables with configuration in name to settings --- .../settings/widgets/anatomy_inputs.py | 8 ++++---- .../tools/settings/settings/widgets/inputs.py | 20 +++++++++---------- .../settings/settings/widgets/widgets.py | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pype/tools/settings/settings/widgets/anatomy_inputs.py b/pype/tools/settings/settings/widgets/anatomy_inputs.py index 09544a2455..0e7e094be5 100644 --- a/pype/tools/settings/settings/widgets/anatomy_inputs.py +++ b/pype/tools/settings/settings/widgets/anatomy_inputs.py @@ -1,10 +1,10 @@ from Qt import QtWidgets, QtCore from .widgets import ExpandingWidget -from .inputs import ConfigObject, ModifiableDict, PathWidget, RawJsonWidget +from .inputs import SettingObject, ModifiableDict, PathWidget, RawJsonWidget from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET, METADATA_KEY -class AnatomyWidget(QtWidgets.QWidget, ConfigObject): +class AnatomyWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) template_keys = ( "project[name]", @@ -224,7 +224,7 @@ class AnatomyWidget(QtWidgets.QWidget, ConfigObject): return {self.key: self.item_value()} -class RootsWidget(QtWidgets.QWidget, ConfigObject): +class RootsWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) def __init__(self, input_data, parent): @@ -603,7 +603,7 @@ class RootsWidget(QtWidgets.QWidget, ConfigObject): return {self.key: self.item_value()} -class TemplatesWidget(QtWidgets.QWidget, ConfigObject): +class TemplatesWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) def __init__(self, input_data, parent): diff --git a/pype/tools/settings/settings/widgets/inputs.py b/pype/tools/settings/settings/widgets/inputs.py index fa809cd2d2..aa72f1c81f 100644 --- a/pype/tools/settings/settings/widgets/inputs.py +++ b/pype/tools/settings/settings/widgets/inputs.py @@ -3,7 +3,7 @@ import logging import collections from Qt import QtWidgets, QtCore, QtGui from .widgets import ( - AbstractConfigObject, + AbstractSettingObject, ExpandingWidget, NumberSpinBox, PathInput @@ -11,7 +11,7 @@ from .widgets import ( from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET -class ConfigObject(AbstractConfigObject): +class SettingObject(AbstractSettingObject): default_input_value = NOT_SET allow_actions = True default_state = "" @@ -88,7 +88,7 @@ class ConfigObject(AbstractConfigObject): @property def any_parent_is_group(self): if self._any_parent_is_group is None: - return super(ConfigObject, self).any_parent_is_group + return super(SettingObject, self).any_parent_is_group return self._any_parent_is_group @property @@ -246,7 +246,7 @@ class ConfigObject(AbstractConfigObject): return item.mouseReleaseEvent(self, event) -class InputObject(ConfigObject): +class InputObject(SettingObject): def update_default_values(self, parent_values): self._state = None self._is_modified = False @@ -879,7 +879,7 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): return self.text_input.json_value() -class ListItem(QtWidgets.QWidget, ConfigObject): +class ListItem(QtWidgets.QWidget, SettingObject): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1157,7 +1157,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): return output -class ModifiableDictItem(QtWidgets.QWidget, ConfigObject): +class ModifiableDictItem(QtWidgets.QWidget, SettingObject): _btn_size = 20 value_changed = QtCore.Signal(object) @@ -1534,7 +1534,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): # Dictionaries -class DictWidget(QtWidgets.QWidget, ConfigObject): +class DictWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) def __init__( @@ -1856,7 +1856,7 @@ class DictWidget(QtWidgets.QWidget, ConfigObject): return {self.key: values}, self.is_group -class DictInvisible(QtWidgets.QWidget, ConfigObject): +class DictInvisible(QtWidgets.QWidget, SettingObject): # TODO is not overridable by itself value_changed = QtCore.Signal(object) allow_actions = False @@ -2081,7 +2081,7 @@ class DictInvisible(QtWidgets.QWidget, ConfigObject): return {self.key: values}, self.is_group -class PathWidget(QtWidgets.QWidget, ConfigObject): +class PathWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) platforms = ("windows", "darwin", "linux") platform_labels_mapping = { @@ -2442,7 +2442,7 @@ class FormLabel(QtWidgets.QLabel): self.item = None -class DictFormWidget(QtWidgets.QWidget, ConfigObject): +class DictFormWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) allow_actions = False diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py index aa1f17a7f4..bb42df6026 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -224,7 +224,7 @@ class UnsavedChangesDialog(QtWidgets.QDialog): self.done(2) -class AbstractConfigObject: +class AbstractSettingObject: abstract_attributes = ("_parent", ) def __getattr__(self, name): @@ -232,7 +232,7 @@ class AbstractConfigObject: raise NotImplementedError( "Attribute `{}` is not implemented. {}".format(name, self) ) - return super(AbstractConfigObject, self).__getattribute__(name) + return super(AbstractSettingObject, self).__getattribute__(name) def update_default_values(self, parent_values): raise NotImplementedError( From 111e11b9b15b244659b92baeecf9949648cb0eb3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:20:13 +0200 Subject: [PATCH 900/947] renamed configurations folder in pype co settings --- pype/api.py | 2 +- pype/{configurations => settings}/__init__.py | 0 .../defaults/project_anatomy/colorspace.json | 0 .../defaults/project_anatomy/dataflow.json | 0 .../defaults/project_anatomy/roots.json | 0 .../defaults/project_anatomy/templates.json | 0 .../defaults/project_configurations/ftrack/ftrack_config.json | 0 .../project_configurations/ftrack/ftrack_custom_attributes.json | 0 .../project_configurations/ftrack/partnership_ftrack_cred.json | 0 .../defaults/project_configurations/ftrack/plugins/server.json | 0 .../defaults/project_configurations/ftrack/plugins/user.json | 0 .../project_configurations/ftrack/project_defaults.json | 0 .../defaults/project_configurations/global/creator.json | 0 .../project_configurations/global/project_folder_structure.json | 0 .../defaults/project_configurations/global/sw_folders.json | 0 .../defaults/project_configurations/global/workfiles.json | 0 .../defaults/project_configurations/maya/capture.json | 0 .../project_configurations/muster/templates_mapping.json | 0 .../project_configurations/plugins/celaction/publish.json | 0 .../defaults/project_configurations/plugins/config.json | 0 .../defaults/project_configurations/plugins/ftrack/publish.json | 0 .../defaults/project_configurations/plugins/global/create.json | 0 .../defaults/project_configurations/plugins/global/filter.json | 0 .../defaults/project_configurations/plugins/global/load.json | 0 .../defaults/project_configurations/plugins/global/publish.json | 0 .../defaults/project_configurations/plugins/maya/create.json | 0 .../defaults/project_configurations/plugins/maya/filter.json | 0 .../defaults/project_configurations/plugins/maya/load.json | 0 .../defaults/project_configurations/plugins/maya/publish.json | 0 .../project_configurations/plugins/maya/workfile_build.json | 0 .../defaults/project_configurations/plugins/nuke/create.json | 0 .../defaults/project_configurations/plugins/nuke/load.json | 0 .../defaults/project_configurations/plugins/nuke/publish.json | 0 .../project_configurations/plugins/nuke/workfile_build.json | 0 .../project_configurations/plugins/nukestudio/filter.json | 0 .../project_configurations/plugins/nukestudio/publish.json | 0 .../defaults/project_configurations/plugins/resolve/create.json | 0 .../plugins/standalonepublisher/publish.json | 0 .../defaults/project_configurations/plugins/test/create.json | 0 .../defaults/project_configurations/plugins/test/publish.json | 0 .../defaults/project_configurations/premiere/asset_default.json | 0 .../defaults/project_configurations/premiere/rules_tasks.json | 0 .../project_configurations/standalonepublisher/families.json | 0 .../project_configurations/tools/slates/example_HD.json | 0 .../defaults/project_configurations/unreal/project_setup.json | 0 .../defaults/system_configurations/environments/avalon.json | 0 .../defaults/system_configurations/environments/blender.json | 0 .../defaults/system_configurations/environments/celaction.json | 0 .../defaults/system_configurations/environments/deadline.json | 0 .../defaults/system_configurations/environments/ftrack.json | 0 .../defaults/system_configurations/environments/global.json | 0 .../defaults/system_configurations/environments/harmony.json | 0 .../defaults/system_configurations/environments/houdini.json | 0 .../defaults/system_configurations/environments/maya.json | 0 .../defaults/system_configurations/environments/maya_2018.json | 0 .../defaults/system_configurations/environments/maya_2020.json | 0 .../defaults/system_configurations/environments/mayabatch.json | 0 .../system_configurations/environments/mayabatch_2019.json | 0 .../defaults/system_configurations/environments/mtoa_3.1.1.json | 0 .../defaults/system_configurations/environments/muster.json | 0 .../defaults/system_configurations/environments/nuke.json | 0 .../defaults/system_configurations/environments/nukestudio.json | 0 .../system_configurations/environments/nukestudio_10.0.json | 0 .../defaults/system_configurations/environments/nukex.json | 0 .../defaults/system_configurations/environments/nukex_10.0.json | 0 .../defaults/system_configurations/environments/photoshop.json | 0 .../defaults/system_configurations/environments/premiere.json | 0 .../defaults/system_configurations/environments/resolve.json | 0 .../system_configurations/environments/storyboardpro.json | 0 .../system_configurations/environments/unreal_4.24.json | 0 .../defaults/system_configurations/environments/vray_4300.json | 0 .../defaults/system_configurations/global/applications.json | 0 .../defaults/system_configurations/global/intent.json | 0 .../defaults/system_configurations/global/tools.json | 0 .../defaults/system_configurations/global/tray_modules.json | 0 .../defaults/system_configurations/launchers/blender_2.80.toml | 0 .../defaults/system_configurations/launchers/blender_2.81.toml | 0 .../defaults/system_configurations/launchers/blender_2.82.toml | 0 .../defaults/system_configurations/launchers/blender_2.83.toml | 0 .../system_configurations/launchers/celaction_local.toml | 0 .../system_configurations/launchers/celaction_publish.toml | 0 .../system_configurations/launchers/darwin/blender_2.82 | 0 .../defaults/system_configurations/launchers/darwin/harmony_17 | 0 .../system_configurations/launchers/darwin/harmony_17_launch | 0 .../defaults/system_configurations/launchers/darwin/python3 | 0 .../defaults/system_configurations/launchers/harmony_17.toml | 0 .../defaults/system_configurations/launchers/houdini_16.toml | 0 .../defaults/system_configurations/launchers/houdini_17.toml | 0 .../defaults/system_configurations/launchers/houdini_18.toml | 0 .../defaults/system_configurations/launchers/linux/maya2016 | 0 .../defaults/system_configurations/launchers/linux/maya2017 | 0 .../defaults/system_configurations/launchers/linux/maya2018 | 0 .../defaults/system_configurations/launchers/linux/maya2019 | 0 .../defaults/system_configurations/launchers/linux/maya2020 | 0 .../defaults/system_configurations/launchers/linux/nuke11.3 | 0 .../defaults/system_configurations/launchers/linux/nuke12.0 | 0 .../system_configurations/launchers/linux/nukestudio11.3 | 0 .../system_configurations/launchers/linux/nukestudio12.0 | 0 .../defaults/system_configurations/launchers/linux/nukex11.3 | 0 .../defaults/system_configurations/launchers/linux/nukex12.0 | 0 .../defaults/system_configurations/launchers/maya_2016.toml | 0 .../defaults/system_configurations/launchers/maya_2017.toml | 0 .../defaults/system_configurations/launchers/maya_2018.toml | 0 .../defaults/system_configurations/launchers/maya_2019.toml | 0 .../defaults/system_configurations/launchers/maya_2020.toml | 0 .../system_configurations/launchers/mayabatch_2019.toml | 0 .../system_configurations/launchers/mayabatch_2020.toml | 0 .../defaults/system_configurations/launchers/mayapy2016.toml | 0 .../defaults/system_configurations/launchers/mayapy2017.toml | 0 .../defaults/system_configurations/launchers/mayapy2018.toml | 0 .../defaults/system_configurations/launchers/mayapy2019.toml | 0 .../defaults/system_configurations/launchers/mayapy2020.toml | 0 .../defaults/system_configurations/launchers/myapp.toml | 0 .../defaults/system_configurations/launchers/nuke_10.0.toml | 0 .../defaults/system_configurations/launchers/nuke_11.0.toml | 0 .../defaults/system_configurations/launchers/nuke_11.2.toml | 0 .../defaults/system_configurations/launchers/nuke_11.3.toml | 0 .../defaults/system_configurations/launchers/nuke_12.0.toml | 0 .../system_configurations/launchers/nukestudio_10.0.toml | 0 .../system_configurations/launchers/nukestudio_11.0.toml | 0 .../system_configurations/launchers/nukestudio_11.2.toml | 0 .../system_configurations/launchers/nukestudio_11.3.toml | 0 .../system_configurations/launchers/nukestudio_12.0.toml | 0 .../defaults/system_configurations/launchers/nukex_10.0.toml | 0 .../defaults/system_configurations/launchers/nukex_11.0.toml | 0 .../defaults/system_configurations/launchers/nukex_11.2.toml | 0 .../defaults/system_configurations/launchers/nukex_11.3.toml | 0 .../defaults/system_configurations/launchers/nukex_12.0.toml | 0 .../system_configurations/launchers/photoshop_2020.toml | 0 .../defaults/system_configurations/launchers/premiere_2019.toml | 0 .../defaults/system_configurations/launchers/premiere_2020.toml | 0 .../defaults/system_configurations/launchers/python_2.toml | 0 .../defaults/system_configurations/launchers/python_3.toml | 0 .../defaults/system_configurations/launchers/resolve_16.toml | 0 .../defaults/system_configurations/launchers/shell.toml | 0 .../system_configurations/launchers/storyboardpro_7.toml | 0 .../defaults/system_configurations/launchers/unreal_4.24.toml | 0 .../system_configurations/launchers/windows/blender_2.80.bat | 0 .../system_configurations/launchers/windows/blender_2.81.bat | 0 .../system_configurations/launchers/windows/blender_2.82.bat | 0 .../system_configurations/launchers/windows/blender_2.83.bat | 0 .../system_configurations/launchers/windows/celaction_local.bat | 0 .../launchers/windows/celaction_publish.bat | 0 .../system_configurations/launchers/windows/harmony_17.bat | 0 .../system_configurations/launchers/windows/houdini_16.bat | 0 .../system_configurations/launchers/windows/houdini_17.bat | 0 .../system_configurations/launchers/windows/houdini_18.bat | 0 .../system_configurations/launchers/windows/maya2016.bat | 0 .../system_configurations/launchers/windows/maya2017.bat | 0 .../system_configurations/launchers/windows/maya2018.bat | 0 .../system_configurations/launchers/windows/maya2019.bat | 0 .../system_configurations/launchers/windows/maya2020.bat | 0 .../system_configurations/launchers/windows/mayabatch2019.bat | 0 .../system_configurations/launchers/windows/mayabatch2020.bat | 0 .../system_configurations/launchers/windows/mayapy2016.bat | 0 .../system_configurations/launchers/windows/mayapy2017.bat | 0 .../system_configurations/launchers/windows/mayapy2018.bat | 0 .../system_configurations/launchers/windows/mayapy2019.bat | 0 .../system_configurations/launchers/windows/mayapy2020.bat | 0 .../system_configurations/launchers/windows/nuke10.0.bat | 0 .../system_configurations/launchers/windows/nuke11.0.bat | 0 .../system_configurations/launchers/windows/nuke11.2.bat | 0 .../system_configurations/launchers/windows/nuke11.3.bat | 0 .../system_configurations/launchers/windows/nuke12.0.bat | 0 .../system_configurations/launchers/windows/nukestudio10.0.bat | 0 .../system_configurations/launchers/windows/nukestudio11.0.bat | 0 .../system_configurations/launchers/windows/nukestudio11.2.bat | 0 .../system_configurations/launchers/windows/nukestudio11.3.bat | 0 .../system_configurations/launchers/windows/nukestudio12.0.bat | 0 .../system_configurations/launchers/windows/nukex10.0.bat | 0 .../system_configurations/launchers/windows/nukex11.0.bat | 0 .../system_configurations/launchers/windows/nukex11.2.bat | 0 .../system_configurations/launchers/windows/nukex11.3.bat | 0 .../system_configurations/launchers/windows/nukex12.0.bat | 0 .../system_configurations/launchers/windows/photoshop_2020.bat | 0 .../launchers/windows/premiere_pro_2019.bat | 0 .../launchers/windows/premiere_pro_2020.bat | 0 .../system_configurations/launchers/windows/python3.bat | 0 .../system_configurations/launchers/windows/resolve_16.bat | 0 .../defaults/system_configurations/launchers/windows/shell.bat | 0 .../system_configurations/launchers/windows/storyboardpro_7.bat | 0 .../defaults/system_configurations/launchers/windows/unreal.bat | 0 .../system_configurations/muster/templates_mapping.json | 0 .../system_configurations/standalone_publish/families.json | 0 pype/{configurations => settings}/lib.py | 0 185 files changed, 1 insertion(+), 1 deletion(-) rename pype/{configurations => settings}/__init__.py (100%) rename pype/{configurations => settings}/defaults/project_anatomy/colorspace.json (100%) rename pype/{configurations => settings}/defaults/project_anatomy/dataflow.json (100%) rename pype/{configurations => settings}/defaults/project_anatomy/roots.json (100%) rename pype/{configurations => settings}/defaults/project_anatomy/templates.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/ftrack/ftrack_config.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/ftrack/ftrack_custom_attributes.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/ftrack/partnership_ftrack_cred.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/ftrack/plugins/server.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/ftrack/plugins/user.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/ftrack/project_defaults.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/global/creator.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/global/project_folder_structure.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/global/sw_folders.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/global/workfiles.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/maya/capture.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/muster/templates_mapping.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/celaction/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/config.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/ftrack/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/global/create.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/global/filter.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/global/load.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/global/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/maya/create.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/maya/filter.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/maya/load.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/maya/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/maya/workfile_build.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/nuke/create.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/nuke/load.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/nuke/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/nuke/workfile_build.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/nukestudio/filter.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/nukestudio/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/resolve/create.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/standalonepublisher/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/test/create.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/plugins/test/publish.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/premiere/asset_default.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/premiere/rules_tasks.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/standalonepublisher/families.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/tools/slates/example_HD.json (100%) rename pype/{configurations => settings}/defaults/project_configurations/unreal/project_setup.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/avalon.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/blender.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/celaction.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/deadline.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/ftrack.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/global.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/harmony.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/houdini.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/maya.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/maya_2018.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/maya_2020.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/mayabatch.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/mayabatch_2019.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/mtoa_3.1.1.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/muster.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/nuke.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/nukestudio.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/nukestudio_10.0.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/nukex.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/nukex_10.0.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/photoshop.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/premiere.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/resolve.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/storyboardpro.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/unreal_4.24.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/environments/vray_4300.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/global/applications.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/global/intent.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/global/tools.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/global/tray_modules.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/blender_2.80.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/blender_2.81.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/blender_2.82.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/blender_2.83.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/celaction_local.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/celaction_publish.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/darwin/blender_2.82 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/darwin/harmony_17 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/darwin/harmony_17_launch (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/darwin/python3 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/harmony_17.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/houdini_16.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/houdini_17.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/houdini_18.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/maya2016 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/maya2017 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/maya2018 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/maya2019 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/maya2020 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/nuke11.3 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/nuke12.0 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/nukestudio11.3 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/nukestudio12.0 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/nukex11.3 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/linux/nukex12.0 (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/maya_2016.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/maya_2017.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/maya_2018.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/maya_2019.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/maya_2020.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayabatch_2019.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayabatch_2020.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayapy2016.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayapy2017.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayapy2018.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayapy2019.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/mayapy2020.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/myapp.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nuke_10.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nuke_11.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nuke_11.2.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nuke_11.3.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nuke_12.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukestudio_10.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukestudio_11.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukestudio_11.2.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukestudio_11.3.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukestudio_12.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukex_10.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukex_11.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukex_11.2.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukex_11.3.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/nukex_12.0.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/photoshop_2020.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/premiere_2019.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/premiere_2020.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/python_2.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/python_3.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/resolve_16.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/shell.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/storyboardpro_7.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/unreal_4.24.toml (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/blender_2.80.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/blender_2.81.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/blender_2.82.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/blender_2.83.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/celaction_local.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/celaction_publish.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/harmony_17.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/houdini_16.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/houdini_17.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/houdini_18.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/maya2016.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/maya2017.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/maya2018.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/maya2019.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/maya2020.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayabatch2019.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayabatch2020.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayapy2016.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayapy2017.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayapy2018.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayapy2019.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/mayapy2020.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nuke10.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nuke11.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nuke11.2.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nuke11.3.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nuke12.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukestudio10.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukestudio11.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukestudio11.2.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukestudio11.3.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukestudio12.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukex10.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukex11.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukex11.2.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukex11.3.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/nukex12.0.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/photoshop_2020.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/python3.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/resolve_16.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/shell.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/storyboardpro_7.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/launchers/windows/unreal.bat (100%) rename pype/{configurations => settings}/defaults/system_configurations/muster/templates_mapping.json (100%) rename pype/{configurations => settings}/defaults/system_configurations/standalone_publish/families.json (100%) rename pype/{configurations => settings}/lib.py (100%) diff --git a/pype/api.py b/pype/api.py index c5cd28d4de..0b3439c4c2 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,4 +1,4 @@ -from .configurations.config import ( +from .settings import ( system_configurations, project_configurations ) diff --git a/pype/configurations/__init__.py b/pype/settings/__init__.py similarity index 100% rename from pype/configurations/__init__.py rename to pype/settings/__init__.py diff --git a/pype/configurations/defaults/project_anatomy/colorspace.json b/pype/settings/defaults/project_anatomy/colorspace.json similarity index 100% rename from pype/configurations/defaults/project_anatomy/colorspace.json rename to pype/settings/defaults/project_anatomy/colorspace.json diff --git a/pype/configurations/defaults/project_anatomy/dataflow.json b/pype/settings/defaults/project_anatomy/dataflow.json similarity index 100% rename from pype/configurations/defaults/project_anatomy/dataflow.json rename to pype/settings/defaults/project_anatomy/dataflow.json diff --git a/pype/configurations/defaults/project_anatomy/roots.json b/pype/settings/defaults/project_anatomy/roots.json similarity index 100% rename from pype/configurations/defaults/project_anatomy/roots.json rename to pype/settings/defaults/project_anatomy/roots.json diff --git a/pype/configurations/defaults/project_anatomy/templates.json b/pype/settings/defaults/project_anatomy/templates.json similarity index 100% rename from pype/configurations/defaults/project_anatomy/templates.json rename to pype/settings/defaults/project_anatomy/templates.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/ftrack_config.json b/pype/settings/defaults/project_configurations/ftrack/ftrack_config.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/ftrack_config.json rename to pype/settings/defaults/project_configurations/ftrack/ftrack_config.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/ftrack_custom_attributes.json b/pype/settings/defaults/project_configurations/ftrack/ftrack_custom_attributes.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/ftrack_custom_attributes.json rename to pype/settings/defaults/project_configurations/ftrack/ftrack_custom_attributes.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/partnership_ftrack_cred.json b/pype/settings/defaults/project_configurations/ftrack/partnership_ftrack_cred.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/partnership_ftrack_cred.json rename to pype/settings/defaults/project_configurations/ftrack/partnership_ftrack_cred.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/plugins/server.json b/pype/settings/defaults/project_configurations/ftrack/plugins/server.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/plugins/server.json rename to pype/settings/defaults/project_configurations/ftrack/plugins/server.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/plugins/user.json b/pype/settings/defaults/project_configurations/ftrack/plugins/user.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/plugins/user.json rename to pype/settings/defaults/project_configurations/ftrack/plugins/user.json diff --git a/pype/configurations/defaults/project_configurations/ftrack/project_defaults.json b/pype/settings/defaults/project_configurations/ftrack/project_defaults.json similarity index 100% rename from pype/configurations/defaults/project_configurations/ftrack/project_defaults.json rename to pype/settings/defaults/project_configurations/ftrack/project_defaults.json diff --git a/pype/configurations/defaults/project_configurations/global/creator.json b/pype/settings/defaults/project_configurations/global/creator.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/creator.json rename to pype/settings/defaults/project_configurations/global/creator.json diff --git a/pype/configurations/defaults/project_configurations/global/project_folder_structure.json b/pype/settings/defaults/project_configurations/global/project_folder_structure.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/project_folder_structure.json rename to pype/settings/defaults/project_configurations/global/project_folder_structure.json diff --git a/pype/configurations/defaults/project_configurations/global/sw_folders.json b/pype/settings/defaults/project_configurations/global/sw_folders.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/sw_folders.json rename to pype/settings/defaults/project_configurations/global/sw_folders.json diff --git a/pype/configurations/defaults/project_configurations/global/workfiles.json b/pype/settings/defaults/project_configurations/global/workfiles.json similarity index 100% rename from pype/configurations/defaults/project_configurations/global/workfiles.json rename to pype/settings/defaults/project_configurations/global/workfiles.json diff --git a/pype/configurations/defaults/project_configurations/maya/capture.json b/pype/settings/defaults/project_configurations/maya/capture.json similarity index 100% rename from pype/configurations/defaults/project_configurations/maya/capture.json rename to pype/settings/defaults/project_configurations/maya/capture.json diff --git a/pype/configurations/defaults/project_configurations/muster/templates_mapping.json b/pype/settings/defaults/project_configurations/muster/templates_mapping.json similarity index 100% rename from pype/configurations/defaults/project_configurations/muster/templates_mapping.json rename to pype/settings/defaults/project_configurations/muster/templates_mapping.json diff --git a/pype/configurations/defaults/project_configurations/plugins/celaction/publish.json b/pype/settings/defaults/project_configurations/plugins/celaction/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/celaction/publish.json rename to pype/settings/defaults/project_configurations/plugins/celaction/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/config.json b/pype/settings/defaults/project_configurations/plugins/config.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/config.json rename to pype/settings/defaults/project_configurations/plugins/config.json diff --git a/pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json b/pype/settings/defaults/project_configurations/plugins/ftrack/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/ftrack/publish.json rename to pype/settings/defaults/project_configurations/plugins/ftrack/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/create.json b/pype/settings/defaults/project_configurations/plugins/global/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/create.json rename to pype/settings/defaults/project_configurations/plugins/global/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/filter.json b/pype/settings/defaults/project_configurations/plugins/global/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/filter.json rename to pype/settings/defaults/project_configurations/plugins/global/filter.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/load.json b/pype/settings/defaults/project_configurations/plugins/global/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/load.json rename to pype/settings/defaults/project_configurations/plugins/global/load.json diff --git a/pype/configurations/defaults/project_configurations/plugins/global/publish.json b/pype/settings/defaults/project_configurations/plugins/global/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/global/publish.json rename to pype/settings/defaults/project_configurations/plugins/global/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/create.json b/pype/settings/defaults/project_configurations/plugins/maya/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/create.json rename to pype/settings/defaults/project_configurations/plugins/maya/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/filter.json b/pype/settings/defaults/project_configurations/plugins/maya/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/filter.json rename to pype/settings/defaults/project_configurations/plugins/maya/filter.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/load.json b/pype/settings/defaults/project_configurations/plugins/maya/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/load.json rename to pype/settings/defaults/project_configurations/plugins/maya/load.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/publish.json b/pype/settings/defaults/project_configurations/plugins/maya/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/publish.json rename to pype/settings/defaults/project_configurations/plugins/maya/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json b/pype/settings/defaults/project_configurations/plugins/maya/workfile_build.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/maya/workfile_build.json rename to pype/settings/defaults/project_configurations/plugins/maya/workfile_build.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/create.json b/pype/settings/defaults/project_configurations/plugins/nuke/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/create.json rename to pype/settings/defaults/project_configurations/plugins/nuke/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/load.json b/pype/settings/defaults/project_configurations/plugins/nuke/load.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/load.json rename to pype/settings/defaults/project_configurations/plugins/nuke/load.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/publish.json b/pype/settings/defaults/project_configurations/plugins/nuke/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/publish.json rename to pype/settings/defaults/project_configurations/plugins/nuke/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json b/pype/settings/defaults/project_configurations/plugins/nuke/workfile_build.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nuke/workfile_build.json rename to pype/settings/defaults/project_configurations/plugins/nuke/workfile_build.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json b/pype/settings/defaults/project_configurations/plugins/nukestudio/filter.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nukestudio/filter.json rename to pype/settings/defaults/project_configurations/plugins/nukestudio/filter.json diff --git a/pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json b/pype/settings/defaults/project_configurations/plugins/nukestudio/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/nukestudio/publish.json rename to pype/settings/defaults/project_configurations/plugins/nukestudio/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/resolve/create.json b/pype/settings/defaults/project_configurations/plugins/resolve/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/resolve/create.json rename to pype/settings/defaults/project_configurations/plugins/resolve/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json b/pype/settings/defaults/project_configurations/plugins/standalonepublisher/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/standalonepublisher/publish.json rename to pype/settings/defaults/project_configurations/plugins/standalonepublisher/publish.json diff --git a/pype/configurations/defaults/project_configurations/plugins/test/create.json b/pype/settings/defaults/project_configurations/plugins/test/create.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/test/create.json rename to pype/settings/defaults/project_configurations/plugins/test/create.json diff --git a/pype/configurations/defaults/project_configurations/plugins/test/publish.json b/pype/settings/defaults/project_configurations/plugins/test/publish.json similarity index 100% rename from pype/configurations/defaults/project_configurations/plugins/test/publish.json rename to pype/settings/defaults/project_configurations/plugins/test/publish.json diff --git a/pype/configurations/defaults/project_configurations/premiere/asset_default.json b/pype/settings/defaults/project_configurations/premiere/asset_default.json similarity index 100% rename from pype/configurations/defaults/project_configurations/premiere/asset_default.json rename to pype/settings/defaults/project_configurations/premiere/asset_default.json diff --git a/pype/configurations/defaults/project_configurations/premiere/rules_tasks.json b/pype/settings/defaults/project_configurations/premiere/rules_tasks.json similarity index 100% rename from pype/configurations/defaults/project_configurations/premiere/rules_tasks.json rename to pype/settings/defaults/project_configurations/premiere/rules_tasks.json diff --git a/pype/configurations/defaults/project_configurations/standalonepublisher/families.json b/pype/settings/defaults/project_configurations/standalonepublisher/families.json similarity index 100% rename from pype/configurations/defaults/project_configurations/standalonepublisher/families.json rename to pype/settings/defaults/project_configurations/standalonepublisher/families.json diff --git a/pype/configurations/defaults/project_configurations/tools/slates/example_HD.json b/pype/settings/defaults/project_configurations/tools/slates/example_HD.json similarity index 100% rename from pype/configurations/defaults/project_configurations/tools/slates/example_HD.json rename to pype/settings/defaults/project_configurations/tools/slates/example_HD.json diff --git a/pype/configurations/defaults/project_configurations/unreal/project_setup.json b/pype/settings/defaults/project_configurations/unreal/project_setup.json similarity index 100% rename from pype/configurations/defaults/project_configurations/unreal/project_setup.json rename to pype/settings/defaults/project_configurations/unreal/project_setup.json diff --git a/pype/configurations/defaults/system_configurations/environments/avalon.json b/pype/settings/defaults/system_configurations/environments/avalon.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/avalon.json rename to pype/settings/defaults/system_configurations/environments/avalon.json diff --git a/pype/configurations/defaults/system_configurations/environments/blender.json b/pype/settings/defaults/system_configurations/environments/blender.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/blender.json rename to pype/settings/defaults/system_configurations/environments/blender.json diff --git a/pype/configurations/defaults/system_configurations/environments/celaction.json b/pype/settings/defaults/system_configurations/environments/celaction.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/celaction.json rename to pype/settings/defaults/system_configurations/environments/celaction.json diff --git a/pype/configurations/defaults/system_configurations/environments/deadline.json b/pype/settings/defaults/system_configurations/environments/deadline.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/deadline.json rename to pype/settings/defaults/system_configurations/environments/deadline.json diff --git a/pype/configurations/defaults/system_configurations/environments/ftrack.json b/pype/settings/defaults/system_configurations/environments/ftrack.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/ftrack.json rename to pype/settings/defaults/system_configurations/environments/ftrack.json diff --git a/pype/configurations/defaults/system_configurations/environments/global.json b/pype/settings/defaults/system_configurations/environments/global.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/global.json rename to pype/settings/defaults/system_configurations/environments/global.json diff --git a/pype/configurations/defaults/system_configurations/environments/harmony.json b/pype/settings/defaults/system_configurations/environments/harmony.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/harmony.json rename to pype/settings/defaults/system_configurations/environments/harmony.json diff --git a/pype/configurations/defaults/system_configurations/environments/houdini.json b/pype/settings/defaults/system_configurations/environments/houdini.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/houdini.json rename to pype/settings/defaults/system_configurations/environments/houdini.json diff --git a/pype/configurations/defaults/system_configurations/environments/maya.json b/pype/settings/defaults/system_configurations/environments/maya.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/maya.json rename to pype/settings/defaults/system_configurations/environments/maya.json diff --git a/pype/configurations/defaults/system_configurations/environments/maya_2018.json b/pype/settings/defaults/system_configurations/environments/maya_2018.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/maya_2018.json rename to pype/settings/defaults/system_configurations/environments/maya_2018.json diff --git a/pype/configurations/defaults/system_configurations/environments/maya_2020.json b/pype/settings/defaults/system_configurations/environments/maya_2020.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/maya_2020.json rename to pype/settings/defaults/system_configurations/environments/maya_2020.json diff --git a/pype/configurations/defaults/system_configurations/environments/mayabatch.json b/pype/settings/defaults/system_configurations/environments/mayabatch.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/mayabatch.json rename to pype/settings/defaults/system_configurations/environments/mayabatch.json diff --git a/pype/configurations/defaults/system_configurations/environments/mayabatch_2019.json b/pype/settings/defaults/system_configurations/environments/mayabatch_2019.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/mayabatch_2019.json rename to pype/settings/defaults/system_configurations/environments/mayabatch_2019.json diff --git a/pype/configurations/defaults/system_configurations/environments/mtoa_3.1.1.json b/pype/settings/defaults/system_configurations/environments/mtoa_3.1.1.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/mtoa_3.1.1.json rename to pype/settings/defaults/system_configurations/environments/mtoa_3.1.1.json diff --git a/pype/configurations/defaults/system_configurations/environments/muster.json b/pype/settings/defaults/system_configurations/environments/muster.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/muster.json rename to pype/settings/defaults/system_configurations/environments/muster.json diff --git a/pype/configurations/defaults/system_configurations/environments/nuke.json b/pype/settings/defaults/system_configurations/environments/nuke.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/nuke.json rename to pype/settings/defaults/system_configurations/environments/nuke.json diff --git a/pype/configurations/defaults/system_configurations/environments/nukestudio.json b/pype/settings/defaults/system_configurations/environments/nukestudio.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/nukestudio.json rename to pype/settings/defaults/system_configurations/environments/nukestudio.json diff --git a/pype/configurations/defaults/system_configurations/environments/nukestudio_10.0.json b/pype/settings/defaults/system_configurations/environments/nukestudio_10.0.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/nukestudio_10.0.json rename to pype/settings/defaults/system_configurations/environments/nukestudio_10.0.json diff --git a/pype/configurations/defaults/system_configurations/environments/nukex.json b/pype/settings/defaults/system_configurations/environments/nukex.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/nukex.json rename to pype/settings/defaults/system_configurations/environments/nukex.json diff --git a/pype/configurations/defaults/system_configurations/environments/nukex_10.0.json b/pype/settings/defaults/system_configurations/environments/nukex_10.0.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/nukex_10.0.json rename to pype/settings/defaults/system_configurations/environments/nukex_10.0.json diff --git a/pype/configurations/defaults/system_configurations/environments/photoshop.json b/pype/settings/defaults/system_configurations/environments/photoshop.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/photoshop.json rename to pype/settings/defaults/system_configurations/environments/photoshop.json diff --git a/pype/configurations/defaults/system_configurations/environments/premiere.json b/pype/settings/defaults/system_configurations/environments/premiere.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/premiere.json rename to pype/settings/defaults/system_configurations/environments/premiere.json diff --git a/pype/configurations/defaults/system_configurations/environments/resolve.json b/pype/settings/defaults/system_configurations/environments/resolve.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/resolve.json rename to pype/settings/defaults/system_configurations/environments/resolve.json diff --git a/pype/configurations/defaults/system_configurations/environments/storyboardpro.json b/pype/settings/defaults/system_configurations/environments/storyboardpro.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/storyboardpro.json rename to pype/settings/defaults/system_configurations/environments/storyboardpro.json diff --git a/pype/configurations/defaults/system_configurations/environments/unreal_4.24.json b/pype/settings/defaults/system_configurations/environments/unreal_4.24.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/unreal_4.24.json rename to pype/settings/defaults/system_configurations/environments/unreal_4.24.json diff --git a/pype/configurations/defaults/system_configurations/environments/vray_4300.json b/pype/settings/defaults/system_configurations/environments/vray_4300.json similarity index 100% rename from pype/configurations/defaults/system_configurations/environments/vray_4300.json rename to pype/settings/defaults/system_configurations/environments/vray_4300.json diff --git a/pype/configurations/defaults/system_configurations/global/applications.json b/pype/settings/defaults/system_configurations/global/applications.json similarity index 100% rename from pype/configurations/defaults/system_configurations/global/applications.json rename to pype/settings/defaults/system_configurations/global/applications.json diff --git a/pype/configurations/defaults/system_configurations/global/intent.json b/pype/settings/defaults/system_configurations/global/intent.json similarity index 100% rename from pype/configurations/defaults/system_configurations/global/intent.json rename to pype/settings/defaults/system_configurations/global/intent.json diff --git a/pype/configurations/defaults/system_configurations/global/tools.json b/pype/settings/defaults/system_configurations/global/tools.json similarity index 100% rename from pype/configurations/defaults/system_configurations/global/tools.json rename to pype/settings/defaults/system_configurations/global/tools.json diff --git a/pype/configurations/defaults/system_configurations/global/tray_modules.json b/pype/settings/defaults/system_configurations/global/tray_modules.json similarity index 100% rename from pype/configurations/defaults/system_configurations/global/tray_modules.json rename to pype/settings/defaults/system_configurations/global/tray_modules.json diff --git a/pype/configurations/defaults/system_configurations/launchers/blender_2.80.toml b/pype/settings/defaults/system_configurations/launchers/blender_2.80.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/blender_2.80.toml rename to pype/settings/defaults/system_configurations/launchers/blender_2.80.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/blender_2.81.toml b/pype/settings/defaults/system_configurations/launchers/blender_2.81.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/blender_2.81.toml rename to pype/settings/defaults/system_configurations/launchers/blender_2.81.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/blender_2.82.toml b/pype/settings/defaults/system_configurations/launchers/blender_2.82.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/blender_2.82.toml rename to pype/settings/defaults/system_configurations/launchers/blender_2.82.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/blender_2.83.toml b/pype/settings/defaults/system_configurations/launchers/blender_2.83.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/blender_2.83.toml rename to pype/settings/defaults/system_configurations/launchers/blender_2.83.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/celaction_local.toml b/pype/settings/defaults/system_configurations/launchers/celaction_local.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/celaction_local.toml rename to pype/settings/defaults/system_configurations/launchers/celaction_local.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/celaction_publish.toml b/pype/settings/defaults/system_configurations/launchers/celaction_publish.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/celaction_publish.toml rename to pype/settings/defaults/system_configurations/launchers/celaction_publish.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/darwin/blender_2.82 b/pype/settings/defaults/system_configurations/launchers/darwin/blender_2.82 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/darwin/blender_2.82 rename to pype/settings/defaults/system_configurations/launchers/darwin/blender_2.82 diff --git a/pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17 b/pype/settings/defaults/system_configurations/launchers/darwin/harmony_17 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17 rename to pype/settings/defaults/system_configurations/launchers/darwin/harmony_17 diff --git a/pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17_launch b/pype/settings/defaults/system_configurations/launchers/darwin/harmony_17_launch similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/darwin/harmony_17_launch rename to pype/settings/defaults/system_configurations/launchers/darwin/harmony_17_launch diff --git a/pype/configurations/defaults/system_configurations/launchers/darwin/python3 b/pype/settings/defaults/system_configurations/launchers/darwin/python3 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/darwin/python3 rename to pype/settings/defaults/system_configurations/launchers/darwin/python3 diff --git a/pype/configurations/defaults/system_configurations/launchers/harmony_17.toml b/pype/settings/defaults/system_configurations/launchers/harmony_17.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/harmony_17.toml rename to pype/settings/defaults/system_configurations/launchers/harmony_17.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/houdini_16.toml b/pype/settings/defaults/system_configurations/launchers/houdini_16.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/houdini_16.toml rename to pype/settings/defaults/system_configurations/launchers/houdini_16.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/houdini_17.toml b/pype/settings/defaults/system_configurations/launchers/houdini_17.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/houdini_17.toml rename to pype/settings/defaults/system_configurations/launchers/houdini_17.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/houdini_18.toml b/pype/settings/defaults/system_configurations/launchers/houdini_18.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/houdini_18.toml rename to pype/settings/defaults/system_configurations/launchers/houdini_18.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/maya2016 b/pype/settings/defaults/system_configurations/launchers/linux/maya2016 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/maya2016 rename to pype/settings/defaults/system_configurations/launchers/linux/maya2016 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/maya2017 b/pype/settings/defaults/system_configurations/launchers/linux/maya2017 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/maya2017 rename to pype/settings/defaults/system_configurations/launchers/linux/maya2017 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/maya2018 b/pype/settings/defaults/system_configurations/launchers/linux/maya2018 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/maya2018 rename to pype/settings/defaults/system_configurations/launchers/linux/maya2018 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/maya2019 b/pype/settings/defaults/system_configurations/launchers/linux/maya2019 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/maya2019 rename to pype/settings/defaults/system_configurations/launchers/linux/maya2019 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/maya2020 b/pype/settings/defaults/system_configurations/launchers/linux/maya2020 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/maya2020 rename to pype/settings/defaults/system_configurations/launchers/linux/maya2020 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/nuke11.3 b/pype/settings/defaults/system_configurations/launchers/linux/nuke11.3 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/nuke11.3 rename to pype/settings/defaults/system_configurations/launchers/linux/nuke11.3 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/nuke12.0 b/pype/settings/defaults/system_configurations/launchers/linux/nuke12.0 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/nuke12.0 rename to pype/settings/defaults/system_configurations/launchers/linux/nuke12.0 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/nukestudio11.3 b/pype/settings/defaults/system_configurations/launchers/linux/nukestudio11.3 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/nukestudio11.3 rename to pype/settings/defaults/system_configurations/launchers/linux/nukestudio11.3 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/nukestudio12.0 b/pype/settings/defaults/system_configurations/launchers/linux/nukestudio12.0 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/nukestudio12.0 rename to pype/settings/defaults/system_configurations/launchers/linux/nukestudio12.0 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/nukex11.3 b/pype/settings/defaults/system_configurations/launchers/linux/nukex11.3 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/nukex11.3 rename to pype/settings/defaults/system_configurations/launchers/linux/nukex11.3 diff --git a/pype/configurations/defaults/system_configurations/launchers/linux/nukex12.0 b/pype/settings/defaults/system_configurations/launchers/linux/nukex12.0 similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/linux/nukex12.0 rename to pype/settings/defaults/system_configurations/launchers/linux/nukex12.0 diff --git a/pype/configurations/defaults/system_configurations/launchers/maya_2016.toml b/pype/settings/defaults/system_configurations/launchers/maya_2016.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/maya_2016.toml rename to pype/settings/defaults/system_configurations/launchers/maya_2016.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/maya_2017.toml b/pype/settings/defaults/system_configurations/launchers/maya_2017.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/maya_2017.toml rename to pype/settings/defaults/system_configurations/launchers/maya_2017.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/maya_2018.toml b/pype/settings/defaults/system_configurations/launchers/maya_2018.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/maya_2018.toml rename to pype/settings/defaults/system_configurations/launchers/maya_2018.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/maya_2019.toml b/pype/settings/defaults/system_configurations/launchers/maya_2019.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/maya_2019.toml rename to pype/settings/defaults/system_configurations/launchers/maya_2019.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/maya_2020.toml b/pype/settings/defaults/system_configurations/launchers/maya_2020.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/maya_2020.toml rename to pype/settings/defaults/system_configurations/launchers/maya_2020.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayabatch_2019.toml b/pype/settings/defaults/system_configurations/launchers/mayabatch_2019.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayabatch_2019.toml rename to pype/settings/defaults/system_configurations/launchers/mayabatch_2019.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayabatch_2020.toml b/pype/settings/defaults/system_configurations/launchers/mayabatch_2020.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayabatch_2020.toml rename to pype/settings/defaults/system_configurations/launchers/mayabatch_2020.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayapy2016.toml b/pype/settings/defaults/system_configurations/launchers/mayapy2016.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayapy2016.toml rename to pype/settings/defaults/system_configurations/launchers/mayapy2016.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayapy2017.toml b/pype/settings/defaults/system_configurations/launchers/mayapy2017.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayapy2017.toml rename to pype/settings/defaults/system_configurations/launchers/mayapy2017.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayapy2018.toml b/pype/settings/defaults/system_configurations/launchers/mayapy2018.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayapy2018.toml rename to pype/settings/defaults/system_configurations/launchers/mayapy2018.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayapy2019.toml b/pype/settings/defaults/system_configurations/launchers/mayapy2019.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayapy2019.toml rename to pype/settings/defaults/system_configurations/launchers/mayapy2019.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/mayapy2020.toml b/pype/settings/defaults/system_configurations/launchers/mayapy2020.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/mayapy2020.toml rename to pype/settings/defaults/system_configurations/launchers/mayapy2020.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/myapp.toml b/pype/settings/defaults/system_configurations/launchers/myapp.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/myapp.toml rename to pype/settings/defaults/system_configurations/launchers/myapp.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nuke_10.0.toml b/pype/settings/defaults/system_configurations/launchers/nuke_10.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nuke_10.0.toml rename to pype/settings/defaults/system_configurations/launchers/nuke_10.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nuke_11.0.toml b/pype/settings/defaults/system_configurations/launchers/nuke_11.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nuke_11.0.toml rename to pype/settings/defaults/system_configurations/launchers/nuke_11.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nuke_11.2.toml b/pype/settings/defaults/system_configurations/launchers/nuke_11.2.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nuke_11.2.toml rename to pype/settings/defaults/system_configurations/launchers/nuke_11.2.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nuke_11.3.toml b/pype/settings/defaults/system_configurations/launchers/nuke_11.3.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nuke_11.3.toml rename to pype/settings/defaults/system_configurations/launchers/nuke_11.3.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nuke_12.0.toml b/pype/settings/defaults/system_configurations/launchers/nuke_12.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nuke_12.0.toml rename to pype/settings/defaults/system_configurations/launchers/nuke_12.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukestudio_10.0.toml b/pype/settings/defaults/system_configurations/launchers/nukestudio_10.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukestudio_10.0.toml rename to pype/settings/defaults/system_configurations/launchers/nukestudio_10.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukestudio_11.0.toml b/pype/settings/defaults/system_configurations/launchers/nukestudio_11.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukestudio_11.0.toml rename to pype/settings/defaults/system_configurations/launchers/nukestudio_11.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukestudio_11.2.toml b/pype/settings/defaults/system_configurations/launchers/nukestudio_11.2.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukestudio_11.2.toml rename to pype/settings/defaults/system_configurations/launchers/nukestudio_11.2.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukestudio_11.3.toml b/pype/settings/defaults/system_configurations/launchers/nukestudio_11.3.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukestudio_11.3.toml rename to pype/settings/defaults/system_configurations/launchers/nukestudio_11.3.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukestudio_12.0.toml b/pype/settings/defaults/system_configurations/launchers/nukestudio_12.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukestudio_12.0.toml rename to pype/settings/defaults/system_configurations/launchers/nukestudio_12.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukex_10.0.toml b/pype/settings/defaults/system_configurations/launchers/nukex_10.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukex_10.0.toml rename to pype/settings/defaults/system_configurations/launchers/nukex_10.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukex_11.0.toml b/pype/settings/defaults/system_configurations/launchers/nukex_11.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukex_11.0.toml rename to pype/settings/defaults/system_configurations/launchers/nukex_11.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukex_11.2.toml b/pype/settings/defaults/system_configurations/launchers/nukex_11.2.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukex_11.2.toml rename to pype/settings/defaults/system_configurations/launchers/nukex_11.2.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukex_11.3.toml b/pype/settings/defaults/system_configurations/launchers/nukex_11.3.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukex_11.3.toml rename to pype/settings/defaults/system_configurations/launchers/nukex_11.3.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/nukex_12.0.toml b/pype/settings/defaults/system_configurations/launchers/nukex_12.0.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/nukex_12.0.toml rename to pype/settings/defaults/system_configurations/launchers/nukex_12.0.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/photoshop_2020.toml b/pype/settings/defaults/system_configurations/launchers/photoshop_2020.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/photoshop_2020.toml rename to pype/settings/defaults/system_configurations/launchers/photoshop_2020.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/premiere_2019.toml b/pype/settings/defaults/system_configurations/launchers/premiere_2019.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/premiere_2019.toml rename to pype/settings/defaults/system_configurations/launchers/premiere_2019.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/premiere_2020.toml b/pype/settings/defaults/system_configurations/launchers/premiere_2020.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/premiere_2020.toml rename to pype/settings/defaults/system_configurations/launchers/premiere_2020.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/python_2.toml b/pype/settings/defaults/system_configurations/launchers/python_2.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/python_2.toml rename to pype/settings/defaults/system_configurations/launchers/python_2.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/python_3.toml b/pype/settings/defaults/system_configurations/launchers/python_3.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/python_3.toml rename to pype/settings/defaults/system_configurations/launchers/python_3.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/resolve_16.toml b/pype/settings/defaults/system_configurations/launchers/resolve_16.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/resolve_16.toml rename to pype/settings/defaults/system_configurations/launchers/resolve_16.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/shell.toml b/pype/settings/defaults/system_configurations/launchers/shell.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/shell.toml rename to pype/settings/defaults/system_configurations/launchers/shell.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/storyboardpro_7.toml b/pype/settings/defaults/system_configurations/launchers/storyboardpro_7.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/storyboardpro_7.toml rename to pype/settings/defaults/system_configurations/launchers/storyboardpro_7.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/unreal_4.24.toml b/pype/settings/defaults/system_configurations/launchers/unreal_4.24.toml similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/unreal_4.24.toml rename to pype/settings/defaults/system_configurations/launchers/unreal_4.24.toml diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.80.bat b/pype/settings/defaults/system_configurations/launchers/windows/blender_2.80.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/blender_2.80.bat rename to pype/settings/defaults/system_configurations/launchers/windows/blender_2.80.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.81.bat b/pype/settings/defaults/system_configurations/launchers/windows/blender_2.81.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/blender_2.81.bat rename to pype/settings/defaults/system_configurations/launchers/windows/blender_2.81.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.82.bat b/pype/settings/defaults/system_configurations/launchers/windows/blender_2.82.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/blender_2.82.bat rename to pype/settings/defaults/system_configurations/launchers/windows/blender_2.82.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/blender_2.83.bat b/pype/settings/defaults/system_configurations/launchers/windows/blender_2.83.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/blender_2.83.bat rename to pype/settings/defaults/system_configurations/launchers/windows/blender_2.83.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/celaction_local.bat b/pype/settings/defaults/system_configurations/launchers/windows/celaction_local.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/celaction_local.bat rename to pype/settings/defaults/system_configurations/launchers/windows/celaction_local.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/celaction_publish.bat b/pype/settings/defaults/system_configurations/launchers/windows/celaction_publish.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/celaction_publish.bat rename to pype/settings/defaults/system_configurations/launchers/windows/celaction_publish.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/harmony_17.bat b/pype/settings/defaults/system_configurations/launchers/windows/harmony_17.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/harmony_17.bat rename to pype/settings/defaults/system_configurations/launchers/windows/harmony_17.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/houdini_16.bat b/pype/settings/defaults/system_configurations/launchers/windows/houdini_16.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/houdini_16.bat rename to pype/settings/defaults/system_configurations/launchers/windows/houdini_16.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/houdini_17.bat b/pype/settings/defaults/system_configurations/launchers/windows/houdini_17.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/houdini_17.bat rename to pype/settings/defaults/system_configurations/launchers/windows/houdini_17.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/houdini_18.bat b/pype/settings/defaults/system_configurations/launchers/windows/houdini_18.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/houdini_18.bat rename to pype/settings/defaults/system_configurations/launchers/windows/houdini_18.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/maya2016.bat b/pype/settings/defaults/system_configurations/launchers/windows/maya2016.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/maya2016.bat rename to pype/settings/defaults/system_configurations/launchers/windows/maya2016.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/maya2017.bat b/pype/settings/defaults/system_configurations/launchers/windows/maya2017.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/maya2017.bat rename to pype/settings/defaults/system_configurations/launchers/windows/maya2017.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/maya2018.bat b/pype/settings/defaults/system_configurations/launchers/windows/maya2018.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/maya2018.bat rename to pype/settings/defaults/system_configurations/launchers/windows/maya2018.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/maya2019.bat b/pype/settings/defaults/system_configurations/launchers/windows/maya2019.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/maya2019.bat rename to pype/settings/defaults/system_configurations/launchers/windows/maya2019.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/maya2020.bat b/pype/settings/defaults/system_configurations/launchers/windows/maya2020.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/maya2020.bat rename to pype/settings/defaults/system_configurations/launchers/windows/maya2020.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2019.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayabatch2019.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2019.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayabatch2019.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2020.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayabatch2020.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayabatch2020.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayabatch2020.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2016.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayapy2016.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayapy2016.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayapy2016.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2017.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayapy2017.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayapy2017.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayapy2017.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2018.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayapy2018.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayapy2018.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayapy2018.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2019.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayapy2019.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayapy2019.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayapy2019.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/mayapy2020.bat b/pype/settings/defaults/system_configurations/launchers/windows/mayapy2020.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/mayapy2020.bat rename to pype/settings/defaults/system_configurations/launchers/windows/mayapy2020.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nuke10.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nuke10.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nuke10.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nuke10.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nuke11.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nuke11.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nuke11.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nuke11.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nuke11.2.bat b/pype/settings/defaults/system_configurations/launchers/windows/nuke11.2.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nuke11.2.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nuke11.2.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nuke11.3.bat b/pype/settings/defaults/system_configurations/launchers/windows/nuke11.3.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nuke11.3.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nuke11.3.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nuke12.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nuke12.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nuke12.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nuke12.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio10.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukestudio10.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukestudio10.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukestudio10.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.2.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.2.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.2.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.2.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.3.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.3.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukestudio11.3.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.3.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukestudio12.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukestudio12.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukestudio12.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukestudio12.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukex10.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukex10.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukex10.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukex10.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukex11.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukex11.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukex11.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukex11.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukex11.2.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukex11.2.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukex11.2.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukex11.2.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukex11.3.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukex11.3.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukex11.3.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukex11.3.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/nukex12.0.bat b/pype/settings/defaults/system_configurations/launchers/windows/nukex12.0.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/nukex12.0.bat rename to pype/settings/defaults/system_configurations/launchers/windows/nukex12.0.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/photoshop_2020.bat b/pype/settings/defaults/system_configurations/launchers/windows/photoshop_2020.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/photoshop_2020.bat rename to pype/settings/defaults/system_configurations/launchers/windows/photoshop_2020.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat b/pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat rename to pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat b/pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat rename to pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/python3.bat b/pype/settings/defaults/system_configurations/launchers/windows/python3.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/python3.bat rename to pype/settings/defaults/system_configurations/launchers/windows/python3.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/resolve_16.bat b/pype/settings/defaults/system_configurations/launchers/windows/resolve_16.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/resolve_16.bat rename to pype/settings/defaults/system_configurations/launchers/windows/resolve_16.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/shell.bat b/pype/settings/defaults/system_configurations/launchers/windows/shell.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/shell.bat rename to pype/settings/defaults/system_configurations/launchers/windows/shell.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/storyboardpro_7.bat b/pype/settings/defaults/system_configurations/launchers/windows/storyboardpro_7.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/storyboardpro_7.bat rename to pype/settings/defaults/system_configurations/launchers/windows/storyboardpro_7.bat diff --git a/pype/configurations/defaults/system_configurations/launchers/windows/unreal.bat b/pype/settings/defaults/system_configurations/launchers/windows/unreal.bat similarity index 100% rename from pype/configurations/defaults/system_configurations/launchers/windows/unreal.bat rename to pype/settings/defaults/system_configurations/launchers/windows/unreal.bat diff --git a/pype/configurations/defaults/system_configurations/muster/templates_mapping.json b/pype/settings/defaults/system_configurations/muster/templates_mapping.json similarity index 100% rename from pype/configurations/defaults/system_configurations/muster/templates_mapping.json rename to pype/settings/defaults/system_configurations/muster/templates_mapping.json diff --git a/pype/configurations/defaults/system_configurations/standalone_publish/families.json b/pype/settings/defaults/system_configurations/standalone_publish/families.json similarity index 100% rename from pype/configurations/defaults/system_configurations/standalone_publish/families.json rename to pype/settings/defaults/system_configurations/standalone_publish/families.json diff --git a/pype/configurations/lib.py b/pype/settings/lib.py similarity index 100% rename from pype/configurations/lib.py rename to pype/settings/lib.py From 148ec89e94909e7bf08884999bf0ec5f801c2d54 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:23:45 +0200 Subject: [PATCH 901/947] fix few imports --- pype/tools/settings/settings/widgets/base.py | 4 ++-- pype/tools/settings/settings/widgets/lib.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index e1d167a9f2..6a7a65cb0f 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -1,8 +1,8 @@ import os import json from Qt import QtWidgets, QtCore, QtGui -from pype.configurations.lib import ( - SYSTEM_CONFIGURATIONS_KEY, +from pype.settings.lib import ( + SYSTEM_SETTINGS_KEY, SYSTEM_SETTINGS_PATH, PROJECT_SETTINGS_KEY, PROJECT_SETTINGS_PATH, diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index 9f54b090cb..0c3f01cef1 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -1,7 +1,7 @@ import os import json import copy -from pype.configurations.lib import OVERRIDEN_KEY +from pype.settings.lib import OVERRIDEN_KEY from queue import Queue From d0859f4b60077f8a3886225dd81648edf39cd0ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:24:30 +0200 Subject: [PATCH 902/947] renamed folders in defaults --- .../ftrack/ftrack_config.json | 0 .../ftrack/ftrack_custom_attributes.json | 0 .../ftrack/partnership_ftrack_cred.json | 0 .../ftrack/plugins/server.json | 0 .../ftrack/plugins/user.json | 0 .../ftrack/project_defaults.json | 0 .../global/creator.json | 0 .../global/project_folder_structure.json | 0 .../global/sw_folders.json | 0 .../global/workfiles.json | 0 .../maya/capture.json | 0 .../muster/templates_mapping.json | 0 .../plugins/celaction/publish.json | 0 .../plugins/config.json | 0 .../plugins/ftrack/publish.json | 0 .../plugins/global/create.json | 0 .../plugins/global/filter.json | 0 .../plugins/global/load.json | 0 .../plugins/global/publish.json | 0 .../plugins/maya/create.json | 0 .../plugins/maya/filter.json | 0 .../plugins/maya/load.json | 0 .../plugins/maya/publish.json | 0 .../plugins/maya/workfile_build.json | 0 .../plugins/nuke/create.json | 0 .../plugins/nuke/load.json | 0 .../plugins/nuke/publish.json | 0 .../plugins/nuke/workfile_build.json | 0 .../plugins/nukestudio/filter.json | 0 .../plugins/nukestudio/publish.json | 0 .../plugins/resolve/create.json | 0 .../plugins/standalonepublisher/publish.json | 0 .../plugins/test/create.json | 0 .../plugins/test/publish.json | 0 .../premiere/asset_default.json | 0 .../premiere/rules_tasks.json | 0 .../standalonepublisher/families.json | 0 .../tools/slates/example_HD.json | 0 .../unreal/project_setup.json | 0 .../environments/avalon.json | 0 .../environments/blender.json | 0 .../environments/celaction.json | 0 .../environments/deadline.json | 0 .../environments/ftrack.json | 0 .../environments/global.json | 0 .../environments/harmony.json | 0 .../environments/houdini.json | 0 .../environments/maya.json | 0 .../environments/maya_2018.json | 0 .../environments/maya_2020.json | 0 .../environments/mayabatch.json | 0 .../environments/mayabatch_2019.json | 0 .../environments/mtoa_3.1.1.json | 0 .../environments/muster.json | 0 .../environments/nuke.json | 0 .../environments/nukestudio.json | 0 .../environments/nukestudio_10.0.json | 0 .../environments/nukex.json | 0 .../environments/nukex_10.0.json | 0 .../environments/photoshop.json | 0 .../environments/premiere.json | 0 .../environments/resolve.json | 0 .../environments/storyboardpro.json | 0 .../environments/unreal_4.24.json | 0 .../environments/vray_4300.json | 0 .../global/applications.json | 0 .../{system_configurations => system_settings}/global/intent.json | 0 .../{system_configurations => system_settings}/global/tools.json | 0 .../global/tray_modules.json | 0 .../launchers/blender_2.80.toml | 0 .../launchers/blender_2.81.toml | 0 .../launchers/blender_2.82.toml | 0 .../launchers/blender_2.83.toml | 0 .../launchers/celaction_local.toml | 0 .../launchers/celaction_publish.toml | 0 .../launchers/darwin/blender_2.82 | 0 .../launchers/darwin/harmony_17 | 0 .../launchers/darwin/harmony_17_launch | 0 .../launchers/darwin/python3 | 0 .../launchers/harmony_17.toml | 0 .../launchers/houdini_16.toml | 0 .../launchers/houdini_17.toml | 0 .../launchers/houdini_18.toml | 0 .../launchers/linux/maya2016 | 0 .../launchers/linux/maya2017 | 0 .../launchers/linux/maya2018 | 0 .../launchers/linux/maya2019 | 0 .../launchers/linux/maya2020 | 0 .../launchers/linux/nuke11.3 | 0 .../launchers/linux/nuke12.0 | 0 .../launchers/linux/nukestudio11.3 | 0 .../launchers/linux/nukestudio12.0 | 0 .../launchers/linux/nukex11.3 | 0 .../launchers/linux/nukex12.0 | 0 .../launchers/maya_2016.toml | 0 .../launchers/maya_2017.toml | 0 .../launchers/maya_2018.toml | 0 .../launchers/maya_2019.toml | 0 .../launchers/maya_2020.toml | 0 .../launchers/mayabatch_2019.toml | 0 .../launchers/mayabatch_2020.toml | 0 .../launchers/mayapy2016.toml | 0 .../launchers/mayapy2017.toml | 0 .../launchers/mayapy2018.toml | 0 .../launchers/mayapy2019.toml | 0 .../launchers/mayapy2020.toml | 0 .../launchers/myapp.toml | 0 .../launchers/nuke_10.0.toml | 0 .../launchers/nuke_11.0.toml | 0 .../launchers/nuke_11.2.toml | 0 .../launchers/nuke_11.3.toml | 0 .../launchers/nuke_12.0.toml | 0 .../launchers/nukestudio_10.0.toml | 0 .../launchers/nukestudio_11.0.toml | 0 .../launchers/nukestudio_11.2.toml | 0 .../launchers/nukestudio_11.3.toml | 0 .../launchers/nukestudio_12.0.toml | 0 .../launchers/nukex_10.0.toml | 0 .../launchers/nukex_11.0.toml | 0 .../launchers/nukex_11.2.toml | 0 .../launchers/nukex_11.3.toml | 0 .../launchers/nukex_12.0.toml | 0 .../launchers/photoshop_2020.toml | 0 .../launchers/premiere_2019.toml | 0 .../launchers/premiere_2020.toml | 0 .../launchers/python_2.toml | 0 .../launchers/python_3.toml | 0 .../launchers/resolve_16.toml | 0 .../launchers/shell.toml | 0 .../launchers/storyboardpro_7.toml | 0 .../launchers/unreal_4.24.toml | 0 .../launchers/windows/blender_2.80.bat | 0 .../launchers/windows/blender_2.81.bat | 0 .../launchers/windows/blender_2.82.bat | 0 .../launchers/windows/blender_2.83.bat | 0 .../launchers/windows/celaction_local.bat | 0 .../launchers/windows/celaction_publish.bat | 0 .../launchers/windows/harmony_17.bat | 0 .../launchers/windows/houdini_16.bat | 0 .../launchers/windows/houdini_17.bat | 0 .../launchers/windows/houdini_18.bat | 0 .../launchers/windows/maya2016.bat | 0 .../launchers/windows/maya2017.bat | 0 .../launchers/windows/maya2018.bat | 0 .../launchers/windows/maya2019.bat | 0 .../launchers/windows/maya2020.bat | 0 .../launchers/windows/mayabatch2019.bat | 0 .../launchers/windows/mayabatch2020.bat | 0 .../launchers/windows/mayapy2016.bat | 0 .../launchers/windows/mayapy2017.bat | 0 .../launchers/windows/mayapy2018.bat | 0 .../launchers/windows/mayapy2019.bat | 0 .../launchers/windows/mayapy2020.bat | 0 .../launchers/windows/nuke10.0.bat | 0 .../launchers/windows/nuke11.0.bat | 0 .../launchers/windows/nuke11.2.bat | 0 .../launchers/windows/nuke11.3.bat | 0 .../launchers/windows/nuke12.0.bat | 0 .../launchers/windows/nukestudio10.0.bat | 0 .../launchers/windows/nukestudio11.0.bat | 0 .../launchers/windows/nukestudio11.2.bat | 0 .../launchers/windows/nukestudio11.3.bat | 0 .../launchers/windows/nukestudio12.0.bat | 0 .../launchers/windows/nukex10.0.bat | 0 .../launchers/windows/nukex11.0.bat | 0 .../launchers/windows/nukex11.2.bat | 0 .../launchers/windows/nukex11.3.bat | 0 .../launchers/windows/nukex12.0.bat | 0 .../launchers/windows/photoshop_2020.bat | 0 .../launchers/windows/premiere_pro_2019.bat | 0 .../launchers/windows/premiere_pro_2020.bat | 0 .../launchers/windows/python3.bat | 0 .../launchers/windows/resolve_16.bat | 0 .../launchers/windows/shell.bat | 0 .../launchers/windows/storyboardpro_7.bat | 0 .../launchers/windows/unreal.bat | 0 .../muster/templates_mapping.json | 0 .../standalone_publish/families.json | 0 178 files changed, 0 insertions(+), 0 deletions(-) rename pype/settings/defaults/{project_configurations => project_settings}/ftrack/ftrack_config.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/ftrack/ftrack_custom_attributes.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/ftrack/partnership_ftrack_cred.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/ftrack/plugins/server.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/ftrack/plugins/user.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/ftrack/project_defaults.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/global/creator.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/global/project_folder_structure.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/global/sw_folders.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/global/workfiles.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/maya/capture.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/muster/templates_mapping.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/celaction/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/config.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/ftrack/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/global/create.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/global/filter.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/global/load.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/global/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/maya/create.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/maya/filter.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/maya/load.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/maya/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/maya/workfile_build.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/nuke/create.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/nuke/load.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/nuke/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/nuke/workfile_build.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/nukestudio/filter.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/nukestudio/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/resolve/create.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/standalonepublisher/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/test/create.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/plugins/test/publish.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/premiere/asset_default.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/premiere/rules_tasks.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/standalonepublisher/families.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/tools/slates/example_HD.json (100%) rename pype/settings/defaults/{project_configurations => project_settings}/unreal/project_setup.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/avalon.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/blender.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/celaction.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/deadline.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/ftrack.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/global.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/harmony.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/houdini.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/maya.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/maya_2018.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/maya_2020.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/mayabatch.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/mayabatch_2019.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/mtoa_3.1.1.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/muster.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/nuke.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/nukestudio.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/nukestudio_10.0.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/nukex.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/nukex_10.0.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/photoshop.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/premiere.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/resolve.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/storyboardpro.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/unreal_4.24.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/environments/vray_4300.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/global/applications.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/global/intent.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/global/tools.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/global/tray_modules.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/blender_2.80.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/blender_2.81.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/blender_2.82.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/blender_2.83.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/celaction_local.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/celaction_publish.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/darwin/blender_2.82 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/darwin/harmony_17 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/darwin/harmony_17_launch (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/darwin/python3 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/harmony_17.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/houdini_16.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/houdini_17.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/houdini_18.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/maya2016 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/maya2017 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/maya2018 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/maya2019 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/maya2020 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/nuke11.3 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/nuke12.0 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/nukestudio11.3 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/nukestudio12.0 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/nukex11.3 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/linux/nukex12.0 (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/maya_2016.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/maya_2017.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/maya_2018.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/maya_2019.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/maya_2020.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayabatch_2019.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayabatch_2020.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayapy2016.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayapy2017.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayapy2018.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayapy2019.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/mayapy2020.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/myapp.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nuke_10.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nuke_11.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nuke_11.2.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nuke_11.3.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nuke_12.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukestudio_10.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukestudio_11.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukestudio_11.2.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukestudio_11.3.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukestudio_12.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukex_10.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukex_11.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukex_11.2.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukex_11.3.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/nukex_12.0.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/photoshop_2020.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/premiere_2019.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/premiere_2020.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/python_2.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/python_3.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/resolve_16.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/shell.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/storyboardpro_7.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/unreal_4.24.toml (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/blender_2.80.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/blender_2.81.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/blender_2.82.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/blender_2.83.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/celaction_local.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/celaction_publish.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/harmony_17.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/houdini_16.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/houdini_17.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/houdini_18.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/maya2016.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/maya2017.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/maya2018.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/maya2019.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/maya2020.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayabatch2019.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayabatch2020.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayapy2016.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayapy2017.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayapy2018.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayapy2019.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/mayapy2020.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nuke10.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nuke11.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nuke11.2.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nuke11.3.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nuke12.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukestudio10.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukestudio11.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukestudio11.2.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukestudio11.3.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukestudio12.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukex10.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukex11.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukex11.2.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukex11.3.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/nukex12.0.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/photoshop_2020.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/premiere_pro_2019.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/premiere_pro_2020.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/python3.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/resolve_16.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/shell.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/storyboardpro_7.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/launchers/windows/unreal.bat (100%) rename pype/settings/defaults/{system_configurations => system_settings}/muster/templates_mapping.json (100%) rename pype/settings/defaults/{system_configurations => system_settings}/standalone_publish/families.json (100%) diff --git a/pype/settings/defaults/project_configurations/ftrack/ftrack_config.json b/pype/settings/defaults/project_settings/ftrack/ftrack_config.json similarity index 100% rename from pype/settings/defaults/project_configurations/ftrack/ftrack_config.json rename to pype/settings/defaults/project_settings/ftrack/ftrack_config.json diff --git a/pype/settings/defaults/project_configurations/ftrack/ftrack_custom_attributes.json b/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json similarity index 100% rename from pype/settings/defaults/project_configurations/ftrack/ftrack_custom_attributes.json rename to pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json diff --git a/pype/settings/defaults/project_configurations/ftrack/partnership_ftrack_cred.json b/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json similarity index 100% rename from pype/settings/defaults/project_configurations/ftrack/partnership_ftrack_cred.json rename to pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json diff --git a/pype/settings/defaults/project_configurations/ftrack/plugins/server.json b/pype/settings/defaults/project_settings/ftrack/plugins/server.json similarity index 100% rename from pype/settings/defaults/project_configurations/ftrack/plugins/server.json rename to pype/settings/defaults/project_settings/ftrack/plugins/server.json diff --git a/pype/settings/defaults/project_configurations/ftrack/plugins/user.json b/pype/settings/defaults/project_settings/ftrack/plugins/user.json similarity index 100% rename from pype/settings/defaults/project_configurations/ftrack/plugins/user.json rename to pype/settings/defaults/project_settings/ftrack/plugins/user.json diff --git a/pype/settings/defaults/project_configurations/ftrack/project_defaults.json b/pype/settings/defaults/project_settings/ftrack/project_defaults.json similarity index 100% rename from pype/settings/defaults/project_configurations/ftrack/project_defaults.json rename to pype/settings/defaults/project_settings/ftrack/project_defaults.json diff --git a/pype/settings/defaults/project_configurations/global/creator.json b/pype/settings/defaults/project_settings/global/creator.json similarity index 100% rename from pype/settings/defaults/project_configurations/global/creator.json rename to pype/settings/defaults/project_settings/global/creator.json diff --git a/pype/settings/defaults/project_configurations/global/project_folder_structure.json b/pype/settings/defaults/project_settings/global/project_folder_structure.json similarity index 100% rename from pype/settings/defaults/project_configurations/global/project_folder_structure.json rename to pype/settings/defaults/project_settings/global/project_folder_structure.json diff --git a/pype/settings/defaults/project_configurations/global/sw_folders.json b/pype/settings/defaults/project_settings/global/sw_folders.json similarity index 100% rename from pype/settings/defaults/project_configurations/global/sw_folders.json rename to pype/settings/defaults/project_settings/global/sw_folders.json diff --git a/pype/settings/defaults/project_configurations/global/workfiles.json b/pype/settings/defaults/project_settings/global/workfiles.json similarity index 100% rename from pype/settings/defaults/project_configurations/global/workfiles.json rename to pype/settings/defaults/project_settings/global/workfiles.json diff --git a/pype/settings/defaults/project_configurations/maya/capture.json b/pype/settings/defaults/project_settings/maya/capture.json similarity index 100% rename from pype/settings/defaults/project_configurations/maya/capture.json rename to pype/settings/defaults/project_settings/maya/capture.json diff --git a/pype/settings/defaults/project_configurations/muster/templates_mapping.json b/pype/settings/defaults/project_settings/muster/templates_mapping.json similarity index 100% rename from pype/settings/defaults/project_configurations/muster/templates_mapping.json rename to pype/settings/defaults/project_settings/muster/templates_mapping.json diff --git a/pype/settings/defaults/project_configurations/plugins/celaction/publish.json b/pype/settings/defaults/project_settings/plugins/celaction/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/celaction/publish.json rename to pype/settings/defaults/project_settings/plugins/celaction/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/config.json b/pype/settings/defaults/project_settings/plugins/config.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/config.json rename to pype/settings/defaults/project_settings/plugins/config.json diff --git a/pype/settings/defaults/project_configurations/plugins/ftrack/publish.json b/pype/settings/defaults/project_settings/plugins/ftrack/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/ftrack/publish.json rename to pype/settings/defaults/project_settings/plugins/ftrack/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/global/create.json b/pype/settings/defaults/project_settings/plugins/global/create.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/global/create.json rename to pype/settings/defaults/project_settings/plugins/global/create.json diff --git a/pype/settings/defaults/project_configurations/plugins/global/filter.json b/pype/settings/defaults/project_settings/plugins/global/filter.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/global/filter.json rename to pype/settings/defaults/project_settings/plugins/global/filter.json diff --git a/pype/settings/defaults/project_configurations/plugins/global/load.json b/pype/settings/defaults/project_settings/plugins/global/load.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/global/load.json rename to pype/settings/defaults/project_settings/plugins/global/load.json diff --git a/pype/settings/defaults/project_configurations/plugins/global/publish.json b/pype/settings/defaults/project_settings/plugins/global/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/global/publish.json rename to pype/settings/defaults/project_settings/plugins/global/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/maya/create.json b/pype/settings/defaults/project_settings/plugins/maya/create.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/maya/create.json rename to pype/settings/defaults/project_settings/plugins/maya/create.json diff --git a/pype/settings/defaults/project_configurations/plugins/maya/filter.json b/pype/settings/defaults/project_settings/plugins/maya/filter.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/maya/filter.json rename to pype/settings/defaults/project_settings/plugins/maya/filter.json diff --git a/pype/settings/defaults/project_configurations/plugins/maya/load.json b/pype/settings/defaults/project_settings/plugins/maya/load.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/maya/load.json rename to pype/settings/defaults/project_settings/plugins/maya/load.json diff --git a/pype/settings/defaults/project_configurations/plugins/maya/publish.json b/pype/settings/defaults/project_settings/plugins/maya/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/maya/publish.json rename to pype/settings/defaults/project_settings/plugins/maya/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/maya/workfile_build.json b/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/maya/workfile_build.json rename to pype/settings/defaults/project_settings/plugins/maya/workfile_build.json diff --git a/pype/settings/defaults/project_configurations/plugins/nuke/create.json b/pype/settings/defaults/project_settings/plugins/nuke/create.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/nuke/create.json rename to pype/settings/defaults/project_settings/plugins/nuke/create.json diff --git a/pype/settings/defaults/project_configurations/plugins/nuke/load.json b/pype/settings/defaults/project_settings/plugins/nuke/load.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/nuke/load.json rename to pype/settings/defaults/project_settings/plugins/nuke/load.json diff --git a/pype/settings/defaults/project_configurations/plugins/nuke/publish.json b/pype/settings/defaults/project_settings/plugins/nuke/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/nuke/publish.json rename to pype/settings/defaults/project_settings/plugins/nuke/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/nuke/workfile_build.json b/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/nuke/workfile_build.json rename to pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json diff --git a/pype/settings/defaults/project_configurations/plugins/nukestudio/filter.json b/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/nukestudio/filter.json rename to pype/settings/defaults/project_settings/plugins/nukestudio/filter.json diff --git a/pype/settings/defaults/project_configurations/plugins/nukestudio/publish.json b/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/nukestudio/publish.json rename to pype/settings/defaults/project_settings/plugins/nukestudio/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/resolve/create.json b/pype/settings/defaults/project_settings/plugins/resolve/create.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/resolve/create.json rename to pype/settings/defaults/project_settings/plugins/resolve/create.json diff --git a/pype/settings/defaults/project_configurations/plugins/standalonepublisher/publish.json b/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/standalonepublisher/publish.json rename to pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json diff --git a/pype/settings/defaults/project_configurations/plugins/test/create.json b/pype/settings/defaults/project_settings/plugins/test/create.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/test/create.json rename to pype/settings/defaults/project_settings/plugins/test/create.json diff --git a/pype/settings/defaults/project_configurations/plugins/test/publish.json b/pype/settings/defaults/project_settings/plugins/test/publish.json similarity index 100% rename from pype/settings/defaults/project_configurations/plugins/test/publish.json rename to pype/settings/defaults/project_settings/plugins/test/publish.json diff --git a/pype/settings/defaults/project_configurations/premiere/asset_default.json b/pype/settings/defaults/project_settings/premiere/asset_default.json similarity index 100% rename from pype/settings/defaults/project_configurations/premiere/asset_default.json rename to pype/settings/defaults/project_settings/premiere/asset_default.json diff --git a/pype/settings/defaults/project_configurations/premiere/rules_tasks.json b/pype/settings/defaults/project_settings/premiere/rules_tasks.json similarity index 100% rename from pype/settings/defaults/project_configurations/premiere/rules_tasks.json rename to pype/settings/defaults/project_settings/premiere/rules_tasks.json diff --git a/pype/settings/defaults/project_configurations/standalonepublisher/families.json b/pype/settings/defaults/project_settings/standalonepublisher/families.json similarity index 100% rename from pype/settings/defaults/project_configurations/standalonepublisher/families.json rename to pype/settings/defaults/project_settings/standalonepublisher/families.json diff --git a/pype/settings/defaults/project_configurations/tools/slates/example_HD.json b/pype/settings/defaults/project_settings/tools/slates/example_HD.json similarity index 100% rename from pype/settings/defaults/project_configurations/tools/slates/example_HD.json rename to pype/settings/defaults/project_settings/tools/slates/example_HD.json diff --git a/pype/settings/defaults/project_configurations/unreal/project_setup.json b/pype/settings/defaults/project_settings/unreal/project_setup.json similarity index 100% rename from pype/settings/defaults/project_configurations/unreal/project_setup.json rename to pype/settings/defaults/project_settings/unreal/project_setup.json diff --git a/pype/settings/defaults/system_configurations/environments/avalon.json b/pype/settings/defaults/system_settings/environments/avalon.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/avalon.json rename to pype/settings/defaults/system_settings/environments/avalon.json diff --git a/pype/settings/defaults/system_configurations/environments/blender.json b/pype/settings/defaults/system_settings/environments/blender.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/blender.json rename to pype/settings/defaults/system_settings/environments/blender.json diff --git a/pype/settings/defaults/system_configurations/environments/celaction.json b/pype/settings/defaults/system_settings/environments/celaction.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/celaction.json rename to pype/settings/defaults/system_settings/environments/celaction.json diff --git a/pype/settings/defaults/system_configurations/environments/deadline.json b/pype/settings/defaults/system_settings/environments/deadline.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/deadline.json rename to pype/settings/defaults/system_settings/environments/deadline.json diff --git a/pype/settings/defaults/system_configurations/environments/ftrack.json b/pype/settings/defaults/system_settings/environments/ftrack.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/ftrack.json rename to pype/settings/defaults/system_settings/environments/ftrack.json diff --git a/pype/settings/defaults/system_configurations/environments/global.json b/pype/settings/defaults/system_settings/environments/global.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/global.json rename to pype/settings/defaults/system_settings/environments/global.json diff --git a/pype/settings/defaults/system_configurations/environments/harmony.json b/pype/settings/defaults/system_settings/environments/harmony.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/harmony.json rename to pype/settings/defaults/system_settings/environments/harmony.json diff --git a/pype/settings/defaults/system_configurations/environments/houdini.json b/pype/settings/defaults/system_settings/environments/houdini.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/houdini.json rename to pype/settings/defaults/system_settings/environments/houdini.json diff --git a/pype/settings/defaults/system_configurations/environments/maya.json b/pype/settings/defaults/system_settings/environments/maya.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/maya.json rename to pype/settings/defaults/system_settings/environments/maya.json diff --git a/pype/settings/defaults/system_configurations/environments/maya_2018.json b/pype/settings/defaults/system_settings/environments/maya_2018.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/maya_2018.json rename to pype/settings/defaults/system_settings/environments/maya_2018.json diff --git a/pype/settings/defaults/system_configurations/environments/maya_2020.json b/pype/settings/defaults/system_settings/environments/maya_2020.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/maya_2020.json rename to pype/settings/defaults/system_settings/environments/maya_2020.json diff --git a/pype/settings/defaults/system_configurations/environments/mayabatch.json b/pype/settings/defaults/system_settings/environments/mayabatch.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/mayabatch.json rename to pype/settings/defaults/system_settings/environments/mayabatch.json diff --git a/pype/settings/defaults/system_configurations/environments/mayabatch_2019.json b/pype/settings/defaults/system_settings/environments/mayabatch_2019.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/mayabatch_2019.json rename to pype/settings/defaults/system_settings/environments/mayabatch_2019.json diff --git a/pype/settings/defaults/system_configurations/environments/mtoa_3.1.1.json b/pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/mtoa_3.1.1.json rename to pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json diff --git a/pype/settings/defaults/system_configurations/environments/muster.json b/pype/settings/defaults/system_settings/environments/muster.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/muster.json rename to pype/settings/defaults/system_settings/environments/muster.json diff --git a/pype/settings/defaults/system_configurations/environments/nuke.json b/pype/settings/defaults/system_settings/environments/nuke.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/nuke.json rename to pype/settings/defaults/system_settings/environments/nuke.json diff --git a/pype/settings/defaults/system_configurations/environments/nukestudio.json b/pype/settings/defaults/system_settings/environments/nukestudio.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/nukestudio.json rename to pype/settings/defaults/system_settings/environments/nukestudio.json diff --git a/pype/settings/defaults/system_configurations/environments/nukestudio_10.0.json b/pype/settings/defaults/system_settings/environments/nukestudio_10.0.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/nukestudio_10.0.json rename to pype/settings/defaults/system_settings/environments/nukestudio_10.0.json diff --git a/pype/settings/defaults/system_configurations/environments/nukex.json b/pype/settings/defaults/system_settings/environments/nukex.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/nukex.json rename to pype/settings/defaults/system_settings/environments/nukex.json diff --git a/pype/settings/defaults/system_configurations/environments/nukex_10.0.json b/pype/settings/defaults/system_settings/environments/nukex_10.0.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/nukex_10.0.json rename to pype/settings/defaults/system_settings/environments/nukex_10.0.json diff --git a/pype/settings/defaults/system_configurations/environments/photoshop.json b/pype/settings/defaults/system_settings/environments/photoshop.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/photoshop.json rename to pype/settings/defaults/system_settings/environments/photoshop.json diff --git a/pype/settings/defaults/system_configurations/environments/premiere.json b/pype/settings/defaults/system_settings/environments/premiere.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/premiere.json rename to pype/settings/defaults/system_settings/environments/premiere.json diff --git a/pype/settings/defaults/system_configurations/environments/resolve.json b/pype/settings/defaults/system_settings/environments/resolve.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/resolve.json rename to pype/settings/defaults/system_settings/environments/resolve.json diff --git a/pype/settings/defaults/system_configurations/environments/storyboardpro.json b/pype/settings/defaults/system_settings/environments/storyboardpro.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/storyboardpro.json rename to pype/settings/defaults/system_settings/environments/storyboardpro.json diff --git a/pype/settings/defaults/system_configurations/environments/unreal_4.24.json b/pype/settings/defaults/system_settings/environments/unreal_4.24.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/unreal_4.24.json rename to pype/settings/defaults/system_settings/environments/unreal_4.24.json diff --git a/pype/settings/defaults/system_configurations/environments/vray_4300.json b/pype/settings/defaults/system_settings/environments/vray_4300.json similarity index 100% rename from pype/settings/defaults/system_configurations/environments/vray_4300.json rename to pype/settings/defaults/system_settings/environments/vray_4300.json diff --git a/pype/settings/defaults/system_configurations/global/applications.json b/pype/settings/defaults/system_settings/global/applications.json similarity index 100% rename from pype/settings/defaults/system_configurations/global/applications.json rename to pype/settings/defaults/system_settings/global/applications.json diff --git a/pype/settings/defaults/system_configurations/global/intent.json b/pype/settings/defaults/system_settings/global/intent.json similarity index 100% rename from pype/settings/defaults/system_configurations/global/intent.json rename to pype/settings/defaults/system_settings/global/intent.json diff --git a/pype/settings/defaults/system_configurations/global/tools.json b/pype/settings/defaults/system_settings/global/tools.json similarity index 100% rename from pype/settings/defaults/system_configurations/global/tools.json rename to pype/settings/defaults/system_settings/global/tools.json diff --git a/pype/settings/defaults/system_configurations/global/tray_modules.json b/pype/settings/defaults/system_settings/global/tray_modules.json similarity index 100% rename from pype/settings/defaults/system_configurations/global/tray_modules.json rename to pype/settings/defaults/system_settings/global/tray_modules.json diff --git a/pype/settings/defaults/system_configurations/launchers/blender_2.80.toml b/pype/settings/defaults/system_settings/launchers/blender_2.80.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/blender_2.80.toml rename to pype/settings/defaults/system_settings/launchers/blender_2.80.toml diff --git a/pype/settings/defaults/system_configurations/launchers/blender_2.81.toml b/pype/settings/defaults/system_settings/launchers/blender_2.81.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/blender_2.81.toml rename to pype/settings/defaults/system_settings/launchers/blender_2.81.toml diff --git a/pype/settings/defaults/system_configurations/launchers/blender_2.82.toml b/pype/settings/defaults/system_settings/launchers/blender_2.82.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/blender_2.82.toml rename to pype/settings/defaults/system_settings/launchers/blender_2.82.toml diff --git a/pype/settings/defaults/system_configurations/launchers/blender_2.83.toml b/pype/settings/defaults/system_settings/launchers/blender_2.83.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/blender_2.83.toml rename to pype/settings/defaults/system_settings/launchers/blender_2.83.toml diff --git a/pype/settings/defaults/system_configurations/launchers/celaction_local.toml b/pype/settings/defaults/system_settings/launchers/celaction_local.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/celaction_local.toml rename to pype/settings/defaults/system_settings/launchers/celaction_local.toml diff --git a/pype/settings/defaults/system_configurations/launchers/celaction_publish.toml b/pype/settings/defaults/system_settings/launchers/celaction_publish.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/celaction_publish.toml rename to pype/settings/defaults/system_settings/launchers/celaction_publish.toml diff --git a/pype/settings/defaults/system_configurations/launchers/darwin/blender_2.82 b/pype/settings/defaults/system_settings/launchers/darwin/blender_2.82 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/darwin/blender_2.82 rename to pype/settings/defaults/system_settings/launchers/darwin/blender_2.82 diff --git a/pype/settings/defaults/system_configurations/launchers/darwin/harmony_17 b/pype/settings/defaults/system_settings/launchers/darwin/harmony_17 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/darwin/harmony_17 rename to pype/settings/defaults/system_settings/launchers/darwin/harmony_17 diff --git a/pype/settings/defaults/system_configurations/launchers/darwin/harmony_17_launch b/pype/settings/defaults/system_settings/launchers/darwin/harmony_17_launch similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/darwin/harmony_17_launch rename to pype/settings/defaults/system_settings/launchers/darwin/harmony_17_launch diff --git a/pype/settings/defaults/system_configurations/launchers/darwin/python3 b/pype/settings/defaults/system_settings/launchers/darwin/python3 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/darwin/python3 rename to pype/settings/defaults/system_settings/launchers/darwin/python3 diff --git a/pype/settings/defaults/system_configurations/launchers/harmony_17.toml b/pype/settings/defaults/system_settings/launchers/harmony_17.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/harmony_17.toml rename to pype/settings/defaults/system_settings/launchers/harmony_17.toml diff --git a/pype/settings/defaults/system_configurations/launchers/houdini_16.toml b/pype/settings/defaults/system_settings/launchers/houdini_16.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/houdini_16.toml rename to pype/settings/defaults/system_settings/launchers/houdini_16.toml diff --git a/pype/settings/defaults/system_configurations/launchers/houdini_17.toml b/pype/settings/defaults/system_settings/launchers/houdini_17.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/houdini_17.toml rename to pype/settings/defaults/system_settings/launchers/houdini_17.toml diff --git a/pype/settings/defaults/system_configurations/launchers/houdini_18.toml b/pype/settings/defaults/system_settings/launchers/houdini_18.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/houdini_18.toml rename to pype/settings/defaults/system_settings/launchers/houdini_18.toml diff --git a/pype/settings/defaults/system_configurations/launchers/linux/maya2016 b/pype/settings/defaults/system_settings/launchers/linux/maya2016 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/maya2016 rename to pype/settings/defaults/system_settings/launchers/linux/maya2016 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/maya2017 b/pype/settings/defaults/system_settings/launchers/linux/maya2017 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/maya2017 rename to pype/settings/defaults/system_settings/launchers/linux/maya2017 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/maya2018 b/pype/settings/defaults/system_settings/launchers/linux/maya2018 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/maya2018 rename to pype/settings/defaults/system_settings/launchers/linux/maya2018 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/maya2019 b/pype/settings/defaults/system_settings/launchers/linux/maya2019 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/maya2019 rename to pype/settings/defaults/system_settings/launchers/linux/maya2019 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/maya2020 b/pype/settings/defaults/system_settings/launchers/linux/maya2020 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/maya2020 rename to pype/settings/defaults/system_settings/launchers/linux/maya2020 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/nuke11.3 b/pype/settings/defaults/system_settings/launchers/linux/nuke11.3 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/nuke11.3 rename to pype/settings/defaults/system_settings/launchers/linux/nuke11.3 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/nuke12.0 b/pype/settings/defaults/system_settings/launchers/linux/nuke12.0 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/nuke12.0 rename to pype/settings/defaults/system_settings/launchers/linux/nuke12.0 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/nukestudio11.3 b/pype/settings/defaults/system_settings/launchers/linux/nukestudio11.3 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/nukestudio11.3 rename to pype/settings/defaults/system_settings/launchers/linux/nukestudio11.3 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/nukestudio12.0 b/pype/settings/defaults/system_settings/launchers/linux/nukestudio12.0 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/nukestudio12.0 rename to pype/settings/defaults/system_settings/launchers/linux/nukestudio12.0 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/nukex11.3 b/pype/settings/defaults/system_settings/launchers/linux/nukex11.3 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/nukex11.3 rename to pype/settings/defaults/system_settings/launchers/linux/nukex11.3 diff --git a/pype/settings/defaults/system_configurations/launchers/linux/nukex12.0 b/pype/settings/defaults/system_settings/launchers/linux/nukex12.0 similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/linux/nukex12.0 rename to pype/settings/defaults/system_settings/launchers/linux/nukex12.0 diff --git a/pype/settings/defaults/system_configurations/launchers/maya_2016.toml b/pype/settings/defaults/system_settings/launchers/maya_2016.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/maya_2016.toml rename to pype/settings/defaults/system_settings/launchers/maya_2016.toml diff --git a/pype/settings/defaults/system_configurations/launchers/maya_2017.toml b/pype/settings/defaults/system_settings/launchers/maya_2017.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/maya_2017.toml rename to pype/settings/defaults/system_settings/launchers/maya_2017.toml diff --git a/pype/settings/defaults/system_configurations/launchers/maya_2018.toml b/pype/settings/defaults/system_settings/launchers/maya_2018.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/maya_2018.toml rename to pype/settings/defaults/system_settings/launchers/maya_2018.toml diff --git a/pype/settings/defaults/system_configurations/launchers/maya_2019.toml b/pype/settings/defaults/system_settings/launchers/maya_2019.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/maya_2019.toml rename to pype/settings/defaults/system_settings/launchers/maya_2019.toml diff --git a/pype/settings/defaults/system_configurations/launchers/maya_2020.toml b/pype/settings/defaults/system_settings/launchers/maya_2020.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/maya_2020.toml rename to pype/settings/defaults/system_settings/launchers/maya_2020.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayabatch_2019.toml b/pype/settings/defaults/system_settings/launchers/mayabatch_2019.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayabatch_2019.toml rename to pype/settings/defaults/system_settings/launchers/mayabatch_2019.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayabatch_2020.toml b/pype/settings/defaults/system_settings/launchers/mayabatch_2020.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayabatch_2020.toml rename to pype/settings/defaults/system_settings/launchers/mayabatch_2020.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayapy2016.toml b/pype/settings/defaults/system_settings/launchers/mayapy2016.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayapy2016.toml rename to pype/settings/defaults/system_settings/launchers/mayapy2016.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayapy2017.toml b/pype/settings/defaults/system_settings/launchers/mayapy2017.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayapy2017.toml rename to pype/settings/defaults/system_settings/launchers/mayapy2017.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayapy2018.toml b/pype/settings/defaults/system_settings/launchers/mayapy2018.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayapy2018.toml rename to pype/settings/defaults/system_settings/launchers/mayapy2018.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayapy2019.toml b/pype/settings/defaults/system_settings/launchers/mayapy2019.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayapy2019.toml rename to pype/settings/defaults/system_settings/launchers/mayapy2019.toml diff --git a/pype/settings/defaults/system_configurations/launchers/mayapy2020.toml b/pype/settings/defaults/system_settings/launchers/mayapy2020.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/mayapy2020.toml rename to pype/settings/defaults/system_settings/launchers/mayapy2020.toml diff --git a/pype/settings/defaults/system_configurations/launchers/myapp.toml b/pype/settings/defaults/system_settings/launchers/myapp.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/myapp.toml rename to pype/settings/defaults/system_settings/launchers/myapp.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nuke_10.0.toml b/pype/settings/defaults/system_settings/launchers/nuke_10.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nuke_10.0.toml rename to pype/settings/defaults/system_settings/launchers/nuke_10.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nuke_11.0.toml b/pype/settings/defaults/system_settings/launchers/nuke_11.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nuke_11.0.toml rename to pype/settings/defaults/system_settings/launchers/nuke_11.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nuke_11.2.toml b/pype/settings/defaults/system_settings/launchers/nuke_11.2.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nuke_11.2.toml rename to pype/settings/defaults/system_settings/launchers/nuke_11.2.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nuke_11.3.toml b/pype/settings/defaults/system_settings/launchers/nuke_11.3.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nuke_11.3.toml rename to pype/settings/defaults/system_settings/launchers/nuke_11.3.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nuke_12.0.toml b/pype/settings/defaults/system_settings/launchers/nuke_12.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nuke_12.0.toml rename to pype/settings/defaults/system_settings/launchers/nuke_12.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukestudio_10.0.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_10.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukestudio_10.0.toml rename to pype/settings/defaults/system_settings/launchers/nukestudio_10.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukestudio_11.0.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_11.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukestudio_11.0.toml rename to pype/settings/defaults/system_settings/launchers/nukestudio_11.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukestudio_11.2.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_11.2.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukestudio_11.2.toml rename to pype/settings/defaults/system_settings/launchers/nukestudio_11.2.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukestudio_11.3.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_11.3.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukestudio_11.3.toml rename to pype/settings/defaults/system_settings/launchers/nukestudio_11.3.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukestudio_12.0.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_12.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukestudio_12.0.toml rename to pype/settings/defaults/system_settings/launchers/nukestudio_12.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukex_10.0.toml b/pype/settings/defaults/system_settings/launchers/nukex_10.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukex_10.0.toml rename to pype/settings/defaults/system_settings/launchers/nukex_10.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukex_11.0.toml b/pype/settings/defaults/system_settings/launchers/nukex_11.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukex_11.0.toml rename to pype/settings/defaults/system_settings/launchers/nukex_11.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukex_11.2.toml b/pype/settings/defaults/system_settings/launchers/nukex_11.2.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukex_11.2.toml rename to pype/settings/defaults/system_settings/launchers/nukex_11.2.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukex_11.3.toml b/pype/settings/defaults/system_settings/launchers/nukex_11.3.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukex_11.3.toml rename to pype/settings/defaults/system_settings/launchers/nukex_11.3.toml diff --git a/pype/settings/defaults/system_configurations/launchers/nukex_12.0.toml b/pype/settings/defaults/system_settings/launchers/nukex_12.0.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/nukex_12.0.toml rename to pype/settings/defaults/system_settings/launchers/nukex_12.0.toml diff --git a/pype/settings/defaults/system_configurations/launchers/photoshop_2020.toml b/pype/settings/defaults/system_settings/launchers/photoshop_2020.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/photoshop_2020.toml rename to pype/settings/defaults/system_settings/launchers/photoshop_2020.toml diff --git a/pype/settings/defaults/system_configurations/launchers/premiere_2019.toml b/pype/settings/defaults/system_settings/launchers/premiere_2019.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/premiere_2019.toml rename to pype/settings/defaults/system_settings/launchers/premiere_2019.toml diff --git a/pype/settings/defaults/system_configurations/launchers/premiere_2020.toml b/pype/settings/defaults/system_settings/launchers/premiere_2020.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/premiere_2020.toml rename to pype/settings/defaults/system_settings/launchers/premiere_2020.toml diff --git a/pype/settings/defaults/system_configurations/launchers/python_2.toml b/pype/settings/defaults/system_settings/launchers/python_2.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/python_2.toml rename to pype/settings/defaults/system_settings/launchers/python_2.toml diff --git a/pype/settings/defaults/system_configurations/launchers/python_3.toml b/pype/settings/defaults/system_settings/launchers/python_3.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/python_3.toml rename to pype/settings/defaults/system_settings/launchers/python_3.toml diff --git a/pype/settings/defaults/system_configurations/launchers/resolve_16.toml b/pype/settings/defaults/system_settings/launchers/resolve_16.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/resolve_16.toml rename to pype/settings/defaults/system_settings/launchers/resolve_16.toml diff --git a/pype/settings/defaults/system_configurations/launchers/shell.toml b/pype/settings/defaults/system_settings/launchers/shell.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/shell.toml rename to pype/settings/defaults/system_settings/launchers/shell.toml diff --git a/pype/settings/defaults/system_configurations/launchers/storyboardpro_7.toml b/pype/settings/defaults/system_settings/launchers/storyboardpro_7.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/storyboardpro_7.toml rename to pype/settings/defaults/system_settings/launchers/storyboardpro_7.toml diff --git a/pype/settings/defaults/system_configurations/launchers/unreal_4.24.toml b/pype/settings/defaults/system_settings/launchers/unreal_4.24.toml similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/unreal_4.24.toml rename to pype/settings/defaults/system_settings/launchers/unreal_4.24.toml diff --git a/pype/settings/defaults/system_configurations/launchers/windows/blender_2.80.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.80.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/blender_2.80.bat rename to pype/settings/defaults/system_settings/launchers/windows/blender_2.80.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/blender_2.81.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.81.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/blender_2.81.bat rename to pype/settings/defaults/system_settings/launchers/windows/blender_2.81.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/blender_2.82.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.82.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/blender_2.82.bat rename to pype/settings/defaults/system_settings/launchers/windows/blender_2.82.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/blender_2.83.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.83.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/blender_2.83.bat rename to pype/settings/defaults/system_settings/launchers/windows/blender_2.83.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/celaction_local.bat b/pype/settings/defaults/system_settings/launchers/windows/celaction_local.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/celaction_local.bat rename to pype/settings/defaults/system_settings/launchers/windows/celaction_local.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/celaction_publish.bat b/pype/settings/defaults/system_settings/launchers/windows/celaction_publish.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/celaction_publish.bat rename to pype/settings/defaults/system_settings/launchers/windows/celaction_publish.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/harmony_17.bat b/pype/settings/defaults/system_settings/launchers/windows/harmony_17.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/harmony_17.bat rename to pype/settings/defaults/system_settings/launchers/windows/harmony_17.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/houdini_16.bat b/pype/settings/defaults/system_settings/launchers/windows/houdini_16.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/houdini_16.bat rename to pype/settings/defaults/system_settings/launchers/windows/houdini_16.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/houdini_17.bat b/pype/settings/defaults/system_settings/launchers/windows/houdini_17.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/houdini_17.bat rename to pype/settings/defaults/system_settings/launchers/windows/houdini_17.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/houdini_18.bat b/pype/settings/defaults/system_settings/launchers/windows/houdini_18.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/houdini_18.bat rename to pype/settings/defaults/system_settings/launchers/windows/houdini_18.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/maya2016.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2016.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/maya2016.bat rename to pype/settings/defaults/system_settings/launchers/windows/maya2016.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/maya2017.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2017.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/maya2017.bat rename to pype/settings/defaults/system_settings/launchers/windows/maya2017.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/maya2018.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2018.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/maya2018.bat rename to pype/settings/defaults/system_settings/launchers/windows/maya2018.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/maya2019.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2019.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/maya2019.bat rename to pype/settings/defaults/system_settings/launchers/windows/maya2019.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/maya2020.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2020.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/maya2020.bat rename to pype/settings/defaults/system_settings/launchers/windows/maya2020.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayabatch2019.bat b/pype/settings/defaults/system_settings/launchers/windows/mayabatch2019.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayabatch2019.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayabatch2019.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayabatch2020.bat b/pype/settings/defaults/system_settings/launchers/windows/mayabatch2020.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayabatch2020.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayabatch2020.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayapy2016.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2016.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayapy2016.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayapy2016.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayapy2017.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2017.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayapy2017.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayapy2017.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayapy2018.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2018.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayapy2018.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayapy2018.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayapy2019.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2019.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayapy2019.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayapy2019.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/mayapy2020.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2020.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/mayapy2020.bat rename to pype/settings/defaults/system_settings/launchers/windows/mayapy2020.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nuke10.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke10.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nuke10.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nuke10.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nuke11.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke11.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nuke11.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nuke11.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nuke11.2.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke11.2.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nuke11.2.bat rename to pype/settings/defaults/system_settings/launchers/windows/nuke11.2.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nuke11.3.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke11.3.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nuke11.3.bat rename to pype/settings/defaults/system_settings/launchers/windows/nuke11.3.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nuke12.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke12.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nuke12.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nuke12.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukestudio10.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio10.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukestudio10.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukestudio10.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukestudio11.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.2.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.2.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.2.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukestudio11.2.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.3.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.3.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukestudio11.3.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukestudio11.3.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukestudio12.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio12.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukestudio12.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukestudio12.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukex10.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex10.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukex10.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukex10.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukex11.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex11.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukex11.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukex11.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukex11.2.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex11.2.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukex11.2.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukex11.2.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukex11.3.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex11.3.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukex11.3.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukex11.3.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/nukex12.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex12.0.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/nukex12.0.bat rename to pype/settings/defaults/system_settings/launchers/windows/nukex12.0.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/photoshop_2020.bat b/pype/settings/defaults/system_settings/launchers/windows/photoshop_2020.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/photoshop_2020.bat rename to pype/settings/defaults/system_settings/launchers/windows/photoshop_2020.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat b/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2019.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2019.bat rename to pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2019.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat b/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2020.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/premiere_pro_2020.bat rename to pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2020.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/python3.bat b/pype/settings/defaults/system_settings/launchers/windows/python3.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/python3.bat rename to pype/settings/defaults/system_settings/launchers/windows/python3.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/resolve_16.bat b/pype/settings/defaults/system_settings/launchers/windows/resolve_16.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/resolve_16.bat rename to pype/settings/defaults/system_settings/launchers/windows/resolve_16.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/shell.bat b/pype/settings/defaults/system_settings/launchers/windows/shell.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/shell.bat rename to pype/settings/defaults/system_settings/launchers/windows/shell.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/storyboardpro_7.bat b/pype/settings/defaults/system_settings/launchers/windows/storyboardpro_7.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/storyboardpro_7.bat rename to pype/settings/defaults/system_settings/launchers/windows/storyboardpro_7.bat diff --git a/pype/settings/defaults/system_configurations/launchers/windows/unreal.bat b/pype/settings/defaults/system_settings/launchers/windows/unreal.bat similarity index 100% rename from pype/settings/defaults/system_configurations/launchers/windows/unreal.bat rename to pype/settings/defaults/system_settings/launchers/windows/unreal.bat diff --git a/pype/settings/defaults/system_configurations/muster/templates_mapping.json b/pype/settings/defaults/system_settings/muster/templates_mapping.json similarity index 100% rename from pype/settings/defaults/system_configurations/muster/templates_mapping.json rename to pype/settings/defaults/system_settings/muster/templates_mapping.json diff --git a/pype/settings/defaults/system_configurations/standalone_publish/families.json b/pype/settings/defaults/system_settings/standalone_publish/families.json similarity index 100% rename from pype/settings/defaults/system_configurations/standalone_publish/families.json rename to pype/settings/defaults/system_settings/standalone_publish/families.json From 5b50b8ede47d0507f56cf4f26701dca0ada4a110 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:31:47 +0200 Subject: [PATCH 903/947] all variables for setting not contain setting instead of configuration --- pype/api.py | 8 ++-- pype/settings/__init__.py | 8 ++-- pype/settings/lib.py | 42 +++++++++---------- .../projects_schema/0_project_gui_schema.json | 2 +- pype/tools/settings/settings/widgets/base.py | 26 ++++++------ 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/pype/api.py b/pype/api.py index 0b3439c4c2..021080b4d5 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,6 +1,6 @@ from .settings import ( - system_configurations, - project_configurations + system_settings, + project_settings ) from pypeapp import ( Logger, @@ -53,8 +53,8 @@ from .lib import ( from .lib import _subprocess as subprocess __all__ = [ - "system_configurations", - "project_configurations", + "system_settings", + "project_settings", "Logger", "Anatomy", diff --git a/pype/settings/__init__.py b/pype/settings/__init__.py index e3fc53fcfd..7e73d541a4 100644 --- a/pype/settings/__init__.py +++ b/pype/settings/__init__.py @@ -1,9 +1,9 @@ from .lib import ( - system_configurations, - project_configurations + system_settings, + project_settings ) __all__ = ( - "system_configurations", - "project_configurations" + "system_settings", + "project_settings" ) diff --git a/pype/settings/lib.py b/pype/settings/lib.py index 101b579b14..388557ca9b 100644 --- a/pype/settings/lib.py +++ b/pype/settings/lib.py @@ -14,13 +14,13 @@ POP_KEY = "__pop_key__" STUDIO_OVERRIDES_PATH = os.environ["PYPE_PROJECT_CONFIGS"] # File where studio's system overrides are stored -SYSTEM_SETTINGS_KEY = "system_configurations" +SYSTEM_SETTINGS_KEY = "system_settings" SYSTEM_SETTINGS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, SYSTEM_SETTINGS_KEY + ".json" ) # File where studio's default project overrides are stored -PROJECT_SETTINGS_KEY = "project_configurations" +PROJECT_SETTINGS_KEY = "project_settings" PROJECT_SETTINGS_FILENAME = PROJECT_SETTINGS_KEY + ".json" PROJECT_SETTINGS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, PROJECT_SETTINGS_FILENAME @@ -32,19 +32,19 @@ PROJECT_ANATOMY_PATH = os.path.join( STUDIO_OVERRIDES_PATH, PROJECT_ANATOMY_FILENAME ) -# Path to default configurations +# Path to default settings DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") -# Variable where cache of default configurations are stored +# Variable where cache of default settings are stored _DEFAULT_SETTINGS = None -def reset_default_configurations(): +def reset_default_settings(): global _DEFAULT_SETTINGS _DEFAULT_SETTINGS = None -def default_configuration(): +def default_settings(): global _DEFAULT_SETTINGS if _DEFAULT_SETTINGS is None: _DEFAULT_SETTINGS = load_jsons_from_dir(DEFAULTS_DIR) @@ -156,13 +156,13 @@ def load_jsons_from_dir(path, *args, **kwargs): return output -def studio_system_configurations(): +def studio_system_settings(): if os.path.exists(SYSTEM_SETTINGS_PATH): return load_json(SYSTEM_SETTINGS_PATH) return {} -def studio_project_configurations(): +def studio_project_settings(): if os.path.exists(PROJECT_SETTINGS_PATH): return load_json(PROJECT_SETTINGS_PATH) return {} @@ -190,7 +190,7 @@ def path_to_project_anatomy(project_name): ) -def project_configurations_overrides(project_name): +def project_settings_overrides(project_name): if not project_name: return {} @@ -234,25 +234,25 @@ def merge_overrides(global_dict, override_dict): return global_dict -def apply_overrides(global_presets, project_overrides): - global_presets = copy.deepcopy(global_presets) - if not project_overrides: - return global_presets - return merge_overrides(global_presets, project_overrides) +def apply_overrides(source_data, override_data): + if not override_data: + return source_data + _source_data = copy.deepcopy(source_data) + return merge_overrides(_source_data, override_data) -def system_configurations(): - default_values = default_configuration()["system_configurations"] - studio_values = studio_system_configurations() +def system_settings(): + default_values = default_settings()[SYSTEM_SETTINGS_KEY] + studio_values = studio_system_settings() return apply_overrides(default_values, studio_values) -def project_configurations(project_name): - default_values = default_configuration()["project_configurations"] - studio_values = studio_project_configurations() +def project_settings(project_name): + default_values = default_settings()[PROJECT_SETTINGS_KEY] + studio_values = studio_project_settings() studio_overrides = apply_overrides(default_values, studio_values) - project_overrides = project_configurations_overrides(project_name) + project_overrides = project_settings_overrides(project_name) return apply_overrides(studio_overrides, project_overrides) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json index 3d7cac68fd..fa7c6a366d 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json @@ -18,7 +18,7 @@ ] }, { "type": "dict-invisible", - "key": "project_configurations", + "key": "project_settings", "children": [ { "type": "schema", diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 6a7a65cb0f..90e3aca677 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -11,14 +11,14 @@ from pype.settings.lib import ( DEFAULTS_DIR, - reset_default_configurations, - default_configuration, + reset_default_settings, + default_settings, - studio_system_configurations, - studio_project_configurations, + studio_system_settings, + studio_project_settings, studio_project_anatomy, - project_configurations_overrides, + project_settings_overrides, project_anatomy_overrides, path_to_project_overrides, @@ -179,7 +179,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = all_values["system"] prject_defaults_dir = os.path.join( - DEFAULTS_DIR, SYSTEM_CONFIGURATIONS_KEY + DEFAULTS_DIR, SYSTEM_SETTINGS_KEY ) keys_to_file = lib.file_keys_from_schema(self.schema) for key_sequence in keys_to_file: @@ -200,7 +200,7 @@ class SystemWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(new_values, file_stream, indent=4) - reset_default_configurations() + reset_default_settings() self._update_values() @@ -208,12 +208,12 @@ class SystemWidget(QtWidgets.QWidget): self.ignore_value_changes = True default_values = { - "system": default_configuration()["system_configurations"] + "system": default_settings()[SYSTEM_SETTINGS_KEY] } for input_field in self.input_fields: input_field.update_default_values(default_values) - system_values = {"system": studio_system_configurations()} + system_values = {"system": studio_system_settings()} for input_field in self.input_fields: input_field.update_studio_values(system_values) @@ -462,7 +462,7 @@ class ProjectWidget(QtWidgets.QWidget): _project_anatomy = lib.NOT_SET self.is_overidable = False else: - _project_overrides = project_configurations_overrides(project_name) + _project_overrides = project_settings_overrides(project_name) _project_anatomy = project_anatomy_overrides(project_name) self.is_overidable = True @@ -519,7 +519,7 @@ class ProjectWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(new_values, file_stream, indent=4) - reset_default_configurations() + reset_default_settings() self._update_values() @@ -638,12 +638,12 @@ class ProjectWidget(QtWidgets.QWidget): def _update_values(self): self.ignore_value_changes = True - default_values = {"project": default_configuration()} + default_values = {"project": default_settings()} for input_field in self.input_fields: input_field.update_default_values(default_values) studio_values = {"project": { - PROJECT_SETTINGS_KEY: studio_project_configurations(), + PROJECT_SETTINGS_KEY: studio_project_settings(), PROJECT_ANATOMY_KEY: studio_project_anatomy() }} for input_field in self.input_fields: From ee612be204b6109c4f0672750339eb13a45732fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:35:39 +0200 Subject: [PATCH 904/947] implemented set as studio override for anatomy items --- pype/tools/settings/settings/widgets/anatomy_inputs.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/tools/settings/settings/widgets/anatomy_inputs.py b/pype/tools/settings/settings/widgets/anatomy_inputs.py index 0e7e094be5..50a6022e3c 100644 --- a/pype/tools/settings/settings/widgets/anatomy_inputs.py +++ b/pype/tools/settings/settings/widgets/anatomy_inputs.py @@ -570,6 +570,13 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): self.singleroot_widget.reset_to_pype_default() self._has_studio_override = False + def set_studio_default(self): + if self.is_multiroot: + self.multiroot_widget.reset_to_pype_default() + else: + self.singleroot_widget.reset_to_pype_default() + self._has_studio_override = True + def discard_changes(self): self._is_overriden = self._was_overriden self._is_modified = False @@ -718,6 +725,9 @@ class TemplatesWidget(QtWidgets.QWidget, SettingObject): def reset_to_pype_default(self): self.value_input.reset_to_pype_default() + def set_studio_default(self): + self.value_input.set_studio_default() + def discard_changes(self): self.value_input.discard_changes() From 38e8f5962826bd8d5edd6dc32f290b37234d6a36 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:38:51 +0200 Subject: [PATCH 905/947] renamed inputs to item_types --- pype/tools/settings/settings/widgets/__init__.py | 9 ++++----- .../widgets/{anatomy_inputs.py => anatomy_types.py} | 4 +++- .../settings/widgets/{inputs.py => item_types.py} | 0 3 files changed, 7 insertions(+), 6 deletions(-) rename pype/tools/settings/settings/widgets/{anatomy_inputs.py => anatomy_types.py} (99%) rename pype/tools/settings/settings/widgets/{inputs.py => item_types.py} (100%) diff --git a/pype/tools/settings/settings/widgets/__init__.py b/pype/tools/settings/settings/widgets/__init__.py index 034692898c..e5ea4b3129 100644 --- a/pype/tools/settings/settings/widgets/__init__.py +++ b/pype/tools/settings/settings/widgets/__init__.py @@ -1,10 +1,9 @@ from .window import MainWidget -# TODO properly register inputs to TypeToKlass class -from . import inputs -from . import anatomy_inputs +from . import item_types +from . import anatomy_type __all__ = [ "MainWidget", - "inputs", - "anatomy_inputs" + "item_types", + "anatomy_type" ] diff --git a/pype/tools/settings/settings/widgets/anatomy_inputs.py b/pype/tools/settings/settings/widgets/anatomy_types.py similarity index 99% rename from pype/tools/settings/settings/widgets/anatomy_inputs.py rename to pype/tools/settings/settings/widgets/anatomy_types.py index 50a6022e3c..9433d7a823 100644 --- a/pype/tools/settings/settings/widgets/anatomy_inputs.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -1,6 +1,8 @@ from Qt import QtWidgets, QtCore from .widgets import ExpandingWidget -from .inputs import SettingObject, ModifiableDict, PathWidget, RawJsonWidget +from .item_types import ( + SettingObject, ModifiableDict, PathWidget, RawJsonWidget +) from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET, METADATA_KEY diff --git a/pype/tools/settings/settings/widgets/inputs.py b/pype/tools/settings/settings/widgets/item_types.py similarity index 100% rename from pype/tools/settings/settings/widgets/inputs.py rename to pype/tools/settings/settings/widgets/item_types.py From 0884ba26900cc7ba8e626a7a3d84a8f092a6f5bd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:39:24 +0200 Subject: [PATCH 906/947] fixed typo --- pype/tools/settings/settings/widgets/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/settings/settings/widgets/__init__.py b/pype/tools/settings/settings/widgets/__init__.py index e5ea4b3129..361fd9d23d 100644 --- a/pype/tools/settings/settings/widgets/__init__.py +++ b/pype/tools/settings/settings/widgets/__init__.py @@ -1,9 +1,9 @@ from .window import MainWidget from . import item_types -from . import anatomy_type +from . import anatomy_types __all__ = [ "MainWidget", "item_types", - "anatomy_type" + "anatomy_types" ] From cd36685ef8406a68b0761d5d04f346c3c511addd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:49:15 +0200 Subject: [PATCH 907/947] fix studio overrides for anatomy roots --- pype/tools/settings/settings/widgets/anatomy_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index 9433d7a823..0d94f6f86f 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -519,9 +519,9 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): @property def child_has_studio_override(self): if self.is_multiroot: - return self.multiroot_widget.child_has_studio_override + return self.multiroot_widget.has_studio_override else: - return self.singleroot_widget.child_has_studio_override + return self.singleroot_widget.has_studio_override @property def child_modified(self): From 35beb0a657182657069e2180135118a41a348141 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:54:09 +0200 Subject: [PATCH 908/947] fixed setting pype default for anatomy --- pype/tools/settings/settings/widgets/anatomy_types.py | 5 +++++ pype/tools/settings/settings/widgets/item_types.py | 8 +++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index 0d94f6f86f..dd4b5dc578 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -196,6 +196,10 @@ class AnatomyWidget(QtWidgets.QWidget, SettingObject): self.root_widget.reset_to_pype_default() self.templates_widget.reset_to_pype_default() + def set_studio_default(self): + self.root_widget.set_studio_default() + self.templates_widget.set_studio_default() + def discard_changes(self): self.root_widget.discard_changes() self.templates_widget.discard_changes() @@ -596,6 +600,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): self.singleroot_widget.discard_changes() self._is_modified = self.child_modified + self._has_studio_override = self._had_studio_override def set_as_overriden(self): self._is_overriden = True diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index aa72f1c81f..b0de7910ab 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -194,7 +194,6 @@ class SettingObject(AbstractSettingObject): not self.is_overidable and ( self.has_studio_override - or self.child_has_studio_override ) ): action = QtWidgets.QAction("Reset to pype default") @@ -203,10 +202,9 @@ class SettingObject(AbstractSettingObject): if ( not self.is_overidable - and ( - (self.is_group and not self._had_studio_override) - or self.any_parent_is_group - ) + and not self.is_overriden + and not self.any_parent_is_group + and not self._had_studio_override ): action = QtWidgets.QAction("Set sudio default") actions_mapping[action] = self._set_studio_default From 438aab08662f48f8879136a0f83635bf7be3f298 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 18:56:16 +0200 Subject: [PATCH 909/947] added window title --- pype/tools/settings/settings/widgets/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/tools/settings/settings/widgets/window.py b/pype/tools/settings/settings/widgets/window.py index 5c7a35fa52..f83da8efe0 100644 --- a/pype/tools/settings/settings/widgets/window.py +++ b/pype/tools/settings/settings/widgets/window.py @@ -9,6 +9,7 @@ class MainWidget(QtWidgets.QWidget): def __init__(self, develop, parent=None): super(MainWidget, self).__init__(parent) self.setObjectName("MainWidget") + self.setWindowTitle("Pype Settings") self.resize(self.widget_width, self.widget_height) From 3b69f890a30b15a177d937e6db1deaa59a484e04 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 14 Sep 2020 19:00:07 +0200 Subject: [PATCH 910/947] added refresh to develop mode --- pype/tools/settings/settings/widgets/base.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 90e3aca677..437a74b8c1 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -27,6 +27,7 @@ from pype.settings.lib import ( from .widgets import UnsavedChangesDialog from . import lib from avalon import io +from avalon.vendor import qtawesome class SystemWidget(QtWidgets.QWidget): @@ -65,9 +66,16 @@ class SystemWidget(QtWidgets.QWidget): if self.develop_mode: save_as_default_btn = QtWidgets.QPushButton("Save as Default") - footer_layout.addWidget(save_as_default_btn, 0) save_as_default_btn.clicked.connect(self._save_as_defaults) + refresh_icon = qtawesome.icon("fa.refresh", color="white") + refresh_button = QtWidgets.QPushButton() + refresh_button.setIcon(refresh_icon) + refresh_button.clicked.connect(self._on_refresh) + + footer_layout.addWidget(save_as_default_btn, 0) + footer_layout.addWidget(refresh_button, 0) + save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) @@ -158,6 +166,9 @@ class SystemWidget(QtWidgets.QWidget): self._update_values() + def _on_refresh(self): + self.reset() + def _save_as_defaults(self): output = {} for item in self.input_fields: From d91c890a7d8335e245d3bf2d1f9939b1bf321e04 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 11:23:39 +0200 Subject: [PATCH 911/947] fixed path-widget default value for dev mode --- .../settings/settings/widgets/item_types.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index b0de7910ab..6433956484 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -2136,13 +2136,18 @@ class PathWidget(QtWidgets.QWidget, SettingObject): @property def default_input_value(self): + if self.multipath: + value_type = list + else: + value_type = str + if self.multiplatform: return { - platform: "" + platform: value_type() for platform in self.platforms } else: - return "" + return value_type() def create_gui(self): if not self.multiplatform and not self.multipath: @@ -2204,9 +2209,19 @@ class PathWidget(QtWidgets.QWidget, SettingObject): value = parent_values.get(self.key, NOT_SET) if value is NOT_SET: - raise ValueError( - "Default value is not set. This is implementation BUG." - ) + if self.develop_mode: + value = {self.key: self.default_input_value} + self.defaults_not_set = True + if value is NOT_SET: + raise NotImplementedError(( + "{} Does not have implemented" + " attribute `default_input_value`" + ).format(self)) + + else: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) self.default_value = value self._has_studio_override = False From 67a38042264fc622c9b7bf0833d549d055db3435 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 11:45:13 +0200 Subject: [PATCH 912/947] fixed expandable widget toggle_content --- pype/tools/settings/settings/widgets/widgets.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py index bb42df6026..96f8d2ec17 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -105,7 +105,7 @@ class ExpandingWidget(QtWidgets.QWidget): self.label_widget = label_widget top_part.clicked.connect(self._top_part_clicked) - self.button_toggle.clicked.connect(self.toggle_content) + self.button_toggle.clicked.connect(self._btn_clicked) self.main_layout = QtWidgets.QVBoxLayout(self) self.main_layout.setContentsMargins(0, 0, 0, 0) @@ -123,16 +123,20 @@ class ExpandingWidget(QtWidgets.QWidget): self.main_layout.addWidget(content_widget) self.content_widget = content_widget + def _btn_clicked(self): + self.toggle_content(self.button_toggle.isChecked()) + def _top_part_clicked(self): - self.toggle_content(not self.button_toggle.isChecked()) + self.toggle_content() def toggle_content(self, *args): if self.toolbox_hidden: return + if len(args) > 0: checked = args[0] else: - checked = self.button_toggle.isChecked() + checked = not self.button_toggle.isChecked() arrow_type = QtCore.Qt.RightArrow if checked: arrow_type = QtCore.Qt.DownArrow From c42927120b1fa6a7abf1ee62624666096f905bc2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 11:47:07 +0200 Subject: [PATCH 913/947] changed `expandable` to `collapsable` and `expanded` to `collapsed` --- .../projects_schema/1_plugins_gui_schema.json | 80 +++++++++---------- .../system_schema/1_intents_gui_schema.json | 2 +- .../system_schema/1_tools_gui_schema.json | 2 +- .../system_schema/1_tray_items.json | 8 +- .../settings/settings/widgets/item_types.py | 21 ++--- 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json index c279a6b04a..6bb14463c1 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -1,25 +1,25 @@ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "plugins", "label": "Plugins", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "celaction", "label": "CelAction", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "publish", "label": "Publish plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ExtractCelactionDeadline", "label": "ExtractCelactionDeadline", @@ -66,20 +66,20 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ftrack", "label": "Ftrack", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "publish", "label": "Publish plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "IntegrateFtrackNote", "label": "IntegrateFtrackNote", @@ -106,20 +106,20 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "global", "label": "Global", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "publish", "label": "Publish plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "IntegrateMasterVersion", "label": "IntegrateMasterVersion", @@ -133,7 +133,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ExtractJpegEXR", "label": "ExtractJpegEXR", @@ -163,7 +163,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ExtractReview", "label": "ExtractReview", "checkbox_key": "enabled", @@ -181,7 +181,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ExtractBurnin", "label": "ExtractBurnin", "checkbox_key": "enabled", @@ -193,7 +193,7 @@ "label": "Enabled" }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "options", "label": "Burnin formating options", "children": [ @@ -231,7 +231,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "IntegrateAssetNew", "label": "IntegrateAssetNew", "is_group": true, @@ -244,7 +244,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ProcessSubmittedJobOnFarm", "label": "ProcessSubmittedJobOnFarm", "checkbox_key": "enabled", @@ -274,20 +274,20 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "maya", "label": "Maya", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "publish", "label": "Publish plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ValidateModelName", "label": "Validate Model Name", "checkbox_key": "enabled", @@ -309,7 +309,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ValidateAssemblyName", "label": "Validate Assembly Name", "checkbox_key": "enabled", @@ -323,7 +323,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ValidateShaderName", "label": "ValidateShaderName", "checkbox_key": "enabled", @@ -341,7 +341,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ValidateMeshHasOverlappingUVs", "label": "ValidateMeshHasOverlappingUVs", "checkbox_key": "enabled", @@ -364,20 +364,20 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "nuke", "label": "Nuke", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "create", "label": "Create plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": false, + "collapsable": false, "key": "CreateWriteRender", "label": "CreateWriteRender", "is_group": true, @@ -390,7 +390,7 @@ ] }, { "type": "dict", - "expandable": false, + "collapsable": false, "key": "CreateWritePrerender", "label": "CreateWritePrerender", "is_group": true, @@ -405,14 +405,14 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "publish", "label": "Publish plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ExtractThumbnail", "label": "ExtractThumbnail", @@ -430,7 +430,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ValidateNukeWriteKnobs", "label": "ValidateNukeWriteKnobs", @@ -448,7 +448,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ExtractReviewDataLut", "label": "ExtractReviewDataLut", @@ -462,7 +462,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ExtractReviewDataMov", "label": "ExtractReviewDataMov", @@ -480,7 +480,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "ExtractSlateFrame", "label": "ExtractSlateFrame", "is_group": true, @@ -493,7 +493,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "NukeSubmitDeadline", "label": "NukeSubmitDeadline", "is_group": true, @@ -527,20 +527,20 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "nukestudio", "label": "NukeStudio", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "publish", "label": "Publish plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "CollectInstanceVersion", "label": "Collect Instance Version", @@ -554,7 +554,7 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "checkbox_key": "enabled", "key": "ExtractReviewCutUpVideo", "label": "Extract Review Cut Up Video", @@ -577,20 +577,20 @@ ] }, { "type": "dict", - "expandable": true, + "collapsable": true, "key": "resolve", "label": "DaVinci Resolve", "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "create", "label": "Creator plugins", "is_file": true, "children": [ { "type": "dict", - "expandable": true, + "collapsable": true, "key": "CreateShotClip", "label": "Create Shot Clip", "is_group": true, diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json index a4b5e16fa1..0c252d2ca9 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json @@ -2,7 +2,7 @@ "key": "intent", "type": "dict", "label": "Intent Setting", - "expandable": true, + "collapsable": true, "is_group": true, "is_file": true, "children": [ diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json index bf35326d1c..d9540eeb3e 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json @@ -2,7 +2,7 @@ "key": "tools", "type": "dict", "label": "Tools", - "expandable": true, + "collapsable": true, "is_group": true, "is_file": true, "children": [ diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json index 7507050900..6da974a415 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json @@ -2,7 +2,7 @@ "key": "tray_modules", "type": "dict", "label": "Modules", - "expandable": true, + "collapsable": true, "is_group": true, "is_file": true, "children": [ @@ -69,7 +69,7 @@ "type": "dict", "key": "Rest Api", "label": "Rest Api", - "expandable": true, + "collapsable": true, "children": [ { "type": "number", @@ -92,7 +92,7 @@ "type": "dict", "key": "Timers Manager", "label": "Timers Manager", - "expandable": true, + "collapsable": true, "children": [ { "type": "number", @@ -110,7 +110,7 @@ "type": "dict", "key": "Clockify", "label": "Clockify", - "expandable": true, + "collapsable": true, "children": [ { "type": "text", diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 6433956484..729ab3a70f 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -1343,14 +1343,15 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.body_widget = body_widget self.label_widget = body_widget.label_widget - expandable = input_data.get("expandable", True) - if not expandable: - body_widget.hide_toolbox(hide_content=False) - else: - expanded = input_data.get("expanded", False) - if expanded: + collapsable = input_data.get("collapsable", True) + if collapsable: + collapsed = input_data.get("collapsed", True) + if not collapsed: body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + self.body_widget = body_widget self.content_widget = content_widget self.content_layout = content_layout @@ -1590,13 +1591,13 @@ class DictWidget(QtWidgets.QWidget, SettingObject): for child_data in input_data.get("children", []): self.add_children_gui(child_data) - expandable = input_data.get("expandable", True) + collapsable = input_data.get("collapsable", True) if len(self.input_fields) == 1 and self.checkbox_widget: body_widget.hide_toolbox(hide_content=True) - elif expandable: - expanded = input_data.get("expanded", False) - if expanded: + elif collapsable: + collapsed = input_data.get("collapsed", True) + if not collapsed: body_widget.toggle_content() else: body_widget.hide_toolbox(hide_content=False) From 0a474167663ac6001ea92539cf685ceda8defcd3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 11:47:36 +0200 Subject: [PATCH 914/947] modifiable dict fixed states --- .../settings/settings/widgets/item_types.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 729ab3a70f..2b6bcfe34c 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -1165,7 +1165,6 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): self.set_default_attributes() self._parent = config_parent - self.is_single = False self.is_key_duplicated = False layout = QtWidgets.QHBoxLayout(self) @@ -1212,7 +1211,13 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def key_value(self): return self.key_input.text() + def _is_enabled(self): + return self.key_input.isEnabled() + def is_key_invalid(self): + if not self._is_enabled(): + return False + if self.key_value() == "": return True @@ -1244,7 +1249,7 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): return self._parent.is_group def on_add_clicked(self): - if self.value_input.isEnabled(): + if self._is_enabled(): self._parent.add_row(row=self.row() + 1) else: self.set_as_empty(False) @@ -1278,15 +1283,17 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): @property def is_invalid(self): + if not self._is_enabled(): + return False return self.is_key_invalid() or self.value_input.is_invalid def update_style(self): - if self.is_key_invalid(): - state = "invalid" - elif self.is_key_modified(): - state = "modified" - else: - state = "" + state = "" + if self._is_enabled(): + if self.is_key_invalid(): + state = "invalid" + elif self.is_key_modified(): + state = "modified" self.key_input.setProperty("state", state) self.key_input.style().polish(self.key_input) From 703e17b35a2654e9696858acf6c0fd3679ef0532 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 11:53:57 +0200 Subject: [PATCH 915/947] removed bold style on label for invalid value --- pype/tools/settings/settings/style/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/settings/settings/style/style.css b/pype/tools/settings/settings/style/style.css index f6dd354935..ab88a2c36f 100644 --- a/pype/tools/settings/settings/style/style.css +++ b/pype/tools/settings/settings/style/style.css @@ -56,8 +56,8 @@ QLabel[state="overriden-modified"] {color: #137cbd;} QLabel[state="overriden-modified"]:hover {color: #1798e8;} QLabel[state="overriden"] {color: #ff8c1a;} QLabel[state="overriden"]:hover {color: #ffa64d;} -QLabel[state="invalid"] {color: #ad2e2e; font-weight: bold;} -QLabel[state="invalid"]:hover {color: #ad2e2e; font-weight: bold;} +QLabel[state="invalid"] {color: #ad2e2e;} +QLabel[state="invalid"]:hover {color: #ad2e2e;} QWidget[input-state="studio"] {border-color: #bfccd6;} From 2d4f983a88674a21f6e05b4398e1b5a4e8ac2c3a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 11:54:12 +0200 Subject: [PATCH 916/947] using only horizontal layouts --- .../system_schema/1_applications_gui_schema.json | 2 +- pype/tools/settings/settings/widgets/item_types.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json index 5ee769eed8..48f8ecbd7c 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json @@ -2,7 +2,7 @@ "key": "applications", "type": "dict", "label": "Applications", - "expandable": true, + "collapsable": true, "is_group": true, "is_file": true, "children": [ diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 2b6bcfe34c..754b4512bd 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -805,9 +805,9 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): self.initial_attributes(input_data, parent, as_widget) - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) + layout.setSpacing(5) self.text_input = RawJsonInput(self) self.text_input.setSizePolicy( @@ -2117,10 +2117,7 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self.input_fields = [] - if not self.multiplatform and not self.multipath: - layout = QtWidgets.QHBoxLayout(self) - else: - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) From 1a7ba6f8d789572d755002cb8fb5362a7e9a6168 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 12:41:59 +0200 Subject: [PATCH 917/947] added `is_input_type` attribute for non input item types --- pype/tools/settings/settings/widgets/item_types.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 754b4512bd..5f995b40b7 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -12,6 +12,7 @@ from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET class SettingObject(AbstractSettingObject): + is_input_type = True default_input_value = NOT_SET allow_actions = True default_state = "" @@ -1612,6 +1613,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject): def add_children_gui(self, child_configuration): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) + + if not klass.is_input_type: + item = klass(child_configuration, self) + self.content_layout.addWidget(item) + return item + if self.checkbox_key and not self.checkbox_widget: key = child_configuration.get("key") if key == self.checkbox_key: @@ -1898,6 +1905,11 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) + if not klass.is_input_type: + item = klass(child_configuration, self) + self.layout().addWidget(item) + return item + item = klass(child_configuration, self) self.layout().addWidget(item) From 0a9c96d27b4d755d7cb641304bad2d43ea82ba9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 12:42:12 +0200 Subject: [PATCH 918/947] added anatomy widgets to TypeToKlass --- pype/tools/settings/settings/widgets/anatomy_types.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index dd4b5dc578..785632b08d 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -754,3 +754,5 @@ class TemplatesWidget(QtWidgets.QWidget, SettingObject): TypeToKlass.types["anatomy"] = AnatomyWidget +TypeToKlass.types["anatomy_roots"] = AnatomyWidget +TypeToKlass.types["anatomy_templates"] = AnatomyWidget From 3bde8a77a2ac5275760fd673d8cb2dc7af21062a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 12:42:27 +0200 Subject: [PATCH 919/947] added examples schema --- .../gui_schemas/system_schema/1_examples.json | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json new file mode 100644 index 0000000000..a884dcb31e --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json @@ -0,0 +1,234 @@ +{ + "key": "example_dict", + "label": "Examples", + "type": "dict", + "is_file": true, + "children": [ + { + "key": "dict_wrapper", + "type": "dict-invisible", + "children": [ + { + "type": "boolean", + "key": "bool", + "label": "Boolean checkbox" + }, { + "type": "label", + "label": "NOTE: This is label" + }, { + "type": "splitter" + }, { + "type": "number", + "key": "integer", + "label": "Integer", + "decimal": 0, + "minimum": 0, + "maximum": 10 + }, { + "type": "number", + "key": "float", + "label": "Float (2 decimals)", + "decimal": 2, + "minimum": -10, + "maximum": -5 + }, { + "type": "text", + "key": "singleline_text", + "label": "Singleline text" + }, { + "type": "text", + "key": "multiline_text", + "label": "Multiline text", + "multiline": true + }, { + "type": "raw-json", + "key": "raw_json", + "label": "Raw json input" + }, { + "type": "list", + "key": "list_item_of_multiline_texts", + "label": "List of multiline texts", + "object_type": "text", + "input_modifiers": { + "multiline": true + } + }, { + "type": "list", + "key": "list_item_of_floats", + "label": "List of floats", + "object_type": "number", + "input_modifiers": { + "decimal": 3, + "minimum": 1000, + "maximum": 2000 + } + }, { + "type": "dict-modifiable", + "key": "modifiable_dict_of_integers", + "label": "Modifiable dict of integers", + "object_type": "number", + "input_modifiers": { + "decimal": 0, + "minimum": 10, + "maximum": 100 + } + }, { + "type": "path-widget", + "key": "single_path_input", + "label": "Single path input", + "multiplatform": false, + "multipath": false + }, { + "type": "path-widget", + "key": "multi_path_input", + "label": "Multi path input", + "multiplatform": false, + "multipath": true + }, { + "type": "path-widget", + "key": "single_os_specific_path_input", + "label": "Single OS specific path input", + "multiplatform": true, + "multipath": false + }, { + "type": "path-widget", + "key": "multi_os_specific_path_input", + "label": "Multi OS specific path input", + "multiplatform": true, + "multipath": true + }, { + "key": "collapsable", + "type": "dict", + "label": "collapsable dictionary", + "collapsable": true, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, { + "key": "collapsable_expanded", + "type": "dict", + "label": "collapsable dictionary, expanded on creation", + "collapsable": true, + "collapsed": false, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, { + "key": "not_collapsable", + "type": "dict", + "label": "Not collapsable", + "collapsable": false, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, { + "key": "nested_dict_lvl1", + "type": "dict", + "label": "Nested dictionary (level 1)", + "children": [ + { + "key": "nested_dict_lvl2", + "type": "dict", + "label": "Nested dictionary (level 2)", + "is_group": true, + "children": [ + { + "key": "nested_dict_lvl3", + "type": "dict", + "label": "Nested dictionary (level 3)", + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, { + "key": "nested_dict_lvl3_2", + "type": "dict", + "label": "Nested dictionary (level 3) (2)", + "children": [ + { + "type": "text", + "key": "_nothing", + "label": "Exmaple input" + }, { + "type": "text", + "key": "_nothing2", + "label": "Exmaple input 2" + } + ] + } + ] + } + ] + }, { + "key": "form_examples", + "type": "dict", + "label": "Form examples", + "children": [ + { + "key": "inputs_without_form_example", + "type": "dict", + "label": "Inputs without form", + "children": [ + { + "type": "text", + "key": "_nothing_1", + "label": "Example label" + }, { + "type": "text", + "key": "_nothing_2", + "label": "Example label ####" + }, { + "type": "text", + "key": "_nothing_3", + "label": "Example label ########" + } + ] + }, { + "key": "inputs_with_form_example", + "type": "dict", + "label": "Inputs with form", + "children": [ + { + "type": "dict-form", + "children": [ + { + "type": "text", + "key": "_nothing_1", + "label": "Example label" + }, { + "type": "text", + "key": "_nothing_2", + "label": "Example label ####" + }, { + "type": "text", + "key": "_nothing_3", + "label": "Example label ########" + } + ] + } + ] + } + ] + } + ] + } + ] +} From 3cf1c91e2b2f4c94e9df67a99885bccfd9a44fca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 12:46:10 +0200 Subject: [PATCH 920/947] implemented label widget and splitter widget --- pype/tools/settings/settings/style/style.css | 4 +++ .../settings/settings/widgets/item_types.py | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pype/tools/settings/settings/style/style.css b/pype/tools/settings/settings/style/style.css index ab88a2c36f..38f69fef50 100644 --- a/pype/tools/settings/settings/style/style.css +++ b/pype/tools/settings/settings/style/style.css @@ -152,6 +152,10 @@ QPushButton[btn-type="expand-toggle"] { background: #141a1f; } +#SplitterItem { + background-color: #1d272f; +} + QTabWidget::pane { border-top-style: none; } diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 5f995b40b7..bd9341973b 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -2668,6 +2668,37 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject): return values, self.is_group +class LabelWidget(QtWidgets.QWidget): + is_input_type = False + + def __init__(self, configuration, parent=None): + super(LabelWidget, self).__init__(parent) + self.setObjectName("LabelWidget") + + label = configuration["label"] + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + label_widget = QtWidgets.QLabel(label, self) + layout.addWidget(label_widget) + + +class SplitterWidget(QtWidgets.QWidget): + is_input_type = False + _height = 2 + + def __init__(self, configuration, parent=None): + super(SplitterWidget, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + splitter_item = QtWidgets.QWidget(self) + splitter_item.setObjectName("SplitterItem") + splitter_item.setMinimumHeight(self._height) + splitter_item.setMaximumHeight(self._height) + layout.addWidget(splitter_item) + + TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["number"] = NumberWidget TypeToKlass.types["text"] = TextWidget @@ -2679,3 +2710,6 @@ TypeToKlass.types["dict"] = DictWidget TypeToKlass.types["dict-invisible"] = DictInvisible TypeToKlass.types["path-widget"] = PathWidget TypeToKlass.types["dict-form"] = DictFormWidget + +TypeToKlass.types["label"] = LabelWidget +TypeToKlass.types["splitter"] = SplitterWidget From 6329836c277d6908a28d5850d7fb121c163b3ed3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 12:46:23 +0200 Subject: [PATCH 921/947] lib fixed is file keys --- pype/tools/settings/settings/widgets/lib.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index 0c3f01cef1..e225d65417 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -123,6 +123,11 @@ class SchemaDuplicatedKeys(Exception): def file_keys_from_schema(schema_data): output = [] + item_type = schema_data["type"] + klass = TypeToKlass.types[item_type] + if not klass.is_input_type: + return output + keys = [] key = schema_data.get("key") if key: @@ -143,6 +148,11 @@ def file_keys_from_schema(schema_data): def validate_all_has_ending_file(schema_data, is_top=True): + item_type = schema_data["type"] + klass = TypeToKlass.types[item_type] + if not klass.is_input_type: + return None + if schema_data.get("is_file"): return None From c4db7bdf620a9fc2c89b890cacfbbeae12518e4e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 12:56:41 +0200 Subject: [PATCH 922/947] mod dict item return valid value if not set --- pype/tools/settings/settings/widgets/item_types.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index bd9341973b..2cc25df270 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -1303,9 +1303,11 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): return self._parent.input_fields.index(self) def config_value(self): - key = self.key_input.text() - value = self.value_input.item_value() - return {key: value} + if self._is_enabled(): + key = self.key_input.text() + value = self.value_input.item_value() + return {key: value} + return {} def mouseReleaseEvent(self, event): return QtWidgets.QWidget.mouseReleaseEvent(self, event) From 50a44e9cbc83817ebb869d562925580957bd3d13 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 13:10:56 +0200 Subject: [PATCH 923/947] modifiable dict has one more addiotional method for getting all values event invalid for roots widget --- .../tools/settings/settings/widgets/item_types.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 2cc25df270..17f8514fb9 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -1302,11 +1302,14 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def row(self): return self._parent.input_fields.index(self) + def item_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + return {key: value} + def config_value(self): if self._is_enabled(): - key = self.key_input.text() - value = self.value_input.item_value() - return {key: value} + return self.item_value() return {} def mouseReleaseEvent(self, event): @@ -1468,6 +1471,12 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self._state = state + def all_item_values(self): + output = {} + for item in self.input_fields: + output.update(item.item_value()) + return output + def item_value(self): output = {} for item in self.input_fields: From d20665a845864791f29f957533d05faa51f0ee1f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 13:18:36 +0200 Subject: [PATCH 924/947] anatomy type fixes --- pype/tools/settings/settings/widgets/anatomy_types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index 785632b08d..6d7b3292ce 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -261,7 +261,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): content_widget = QtWidgets.QWidget(body_widget) path_widget_data = { - "key": "roots", + "key": self.key, "multipath": False, "multiplatform": True } @@ -270,7 +270,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): as_widget=True, parent_widget=content_widget ) multiroot_data = { - "key": "roots", + "key": self.key, "object_type": "path-widget", "expandable": False, "input_modifiers": { @@ -497,7 +497,7 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): self.multiroot_widget.set_value(mutli_value) def _from_multi_to_single(self): - mutli_value = self.multiroot_widget.item_value() + mutli_value = self.multiroot_widget.all_item_values() for value in mutli_value.values(): single_value = value break From 72ae67af1911fdfa31c98babf05367f7843281e0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 13:20:39 +0200 Subject: [PATCH 925/947] fixed PathWidget --- pype/tools/settings/settings/widgets/item_types.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 17f8514fb9..7914c6f04f 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -2234,11 +2234,17 @@ class PathWidget(QtWidgets.QWidget, SettingObject): if self._as_widget: value = parent_values elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if not self.multiplatform: + value = parent_values + else: + value = parent_values.get(self.key, NOT_SET) if value is NOT_SET: if self.develop_mode: - value = {self.key: self.default_input_value} + if self._as_widget or not self.multiplatform: + value = {self.key: self.default_input_value} + else: + value = self.default_input_value self.defaults_not_set = True if value is NOT_SET: raise NotImplementedError(( From acc2e3db0b34e0f40c812ce8a416833e161aae50 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 13:27:02 +0200 Subject: [PATCH 926/947] added refresh button to projects too --- pype/tools/settings/settings/widgets/base.py | 24 ++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 437a74b8c1..d9be84638e 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -111,6 +111,8 @@ class SystemWidget(QtWidgets.QWidget): input_field.hierarchical_style_update() def reset(self): + reset_default_settings() + if self.content_layout.count() != 0: for widget in self.input_fields: self.content_layout.removeWidget(widget) @@ -211,9 +213,7 @@ class SystemWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(new_values, file_stream, indent=4) - reset_default_settings() - - self._update_values() + self.reset() def _update_values(self): self.ignore_value_changes = True @@ -401,9 +401,16 @@ class ProjectWidget(QtWidgets.QWidget): if self.develop_mode: save_as_default_btn = QtWidgets.QPushButton("Save as Default") - footer_layout.addWidget(save_as_default_btn, 0) save_as_default_btn.clicked.connect(self._save_as_defaults) + refresh_icon = qtawesome.icon("fa.refresh", color="white") + refresh_button = QtWidgets.QPushButton() + refresh_button.setIcon(refresh_icon) + refresh_button.clicked.connect(self._on_refresh) + + footer_layout.addWidget(save_as_default_btn, 0) + footer_layout.addWidget(refresh_button, 0) + save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() footer_layout.addWidget(spacer_widget, 1) @@ -453,6 +460,8 @@ class ProjectWidget(QtWidgets.QWidget): input_field.hierarchical_style_update() def reset(self): + reset_default_settings() + self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = self.schema.get("keys", []) self.add_children_gui(self.schema) @@ -530,9 +539,7 @@ class ProjectWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(new_values, file_stream, indent=4) - reset_default_settings() - - self._update_values() + self.reset() def _save(self): has_invalid = False @@ -564,6 +571,9 @@ class ProjectWidget(QtWidgets.QWidget): else: self._save_overrides() + def _on_refresh(self): + self.reset() + def _save_overrides(self): data = {} for item in self.input_fields: From 9b3862eb74d1e1ab71e7ba2f2d1624e3879995ba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 14:38:21 +0200 Subject: [PATCH 927/947] refresh and save as defaults works as expected --- pype/tools/settings/settings/widgets/base.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index d9be84638e..4769cb4e63 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -213,7 +213,10 @@ class SystemWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(new_values, file_stream, indent=4) - self.reset() + reset_default_settings() + + self._update_values() + self.hierarchical_style_update() def _update_values(self): self.ignore_value_changes = True @@ -460,7 +463,11 @@ class ProjectWidget(QtWidgets.QWidget): input_field.hierarchical_style_update() def reset(self): - reset_default_settings() + if self.content_layout.count() != 0: + for widget in self.input_fields: + self.content_layout.removeWidget(widget) + widget.deleteLater() + self.input_fields.clear() self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") self.keys = self.schema.get("keys", []) @@ -539,7 +546,10 @@ class ProjectWidget(QtWidgets.QWidget): with open(output_path, "w") as file_stream: json.dump(new_values, file_stream, indent=4) - self.reset() + reset_default_settings() + + self._update_values() + self.hierarchical_style_update() def _save(self): has_invalid = False From 452e5988f1350fe0cfde66a608dfb37335266e27 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 14:49:47 +0200 Subject: [PATCH 928/947] fixed PathWidget for other udpates --- pype/tools/settings/settings/widgets/item_types.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 7914c6f04f..51ae0fc985 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -2276,7 +2276,10 @@ class PathWidget(QtWidgets.QWidget, SettingObject): if self._as_widget: value = parent_values elif parent_values is not NOT_SET: - value = parent_values.get(self.key, NOT_SET) + if not self.multiplatform: + value = parent_values + else: + value = parent_values.get(self.key, NOT_SET) self.studio_value = value if value is not NOT_SET: @@ -2297,11 +2300,15 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._is_modified = False self._state = None self._child_state = None + override_values = NOT_SET if self._as_widget: override_values = parent_values elif parent_values is not NOT_SET: - override_values = parent_values.get(self.key, override_values) + if not self.multiplatform: + override_values = parent_values + else: + override_values = parent_values.get(self.key, NOT_SET) self._is_overriden = override_values is not NOT_SET self._was_overriden = bool(self._is_overriden) From edce6a1ea9a0fe376e62b26d6f8b0070bcc715c5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 15:19:16 +0200 Subject: [PATCH 929/947] added possibility to hide studio overrides --- pype/tools/settings/settings/widgets/base.py | 62 ++++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 4769cb4e63..dbcc380daf 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -41,6 +41,7 @@ class SystemWidget(QtWidgets.QWidget): super(SystemWidget, self).__init__(parent) self.develop_mode = develop_mode + self._hide_studio_overrides = False self._ignore_value_changes = False self.input_fields = [] @@ -73,8 +74,25 @@ class SystemWidget(QtWidgets.QWidget): refresh_button.setIcon(refresh_icon) refresh_button.clicked.connect(self._on_refresh) + hide_studio_overrides = QtWidgets.QCheckBox() + hide_studio_overrides.setChecked(self._hide_studio_overrides) + hide_studio_overrides.stateChanged.connect( + self._on_hide_studio_overrides + ) + + hide_studio_overrides_widget = QtWidgets.QWidget() + hide_studio_overrides_layout = QtWidgets.QHBoxLayout( + hide_studio_overrides_widget + ) + _label_widget = QtWidgets.QLabel( + "Hide studio overrides", hide_studio_overrides_widget + ) + hide_studio_overrides_layout.addWidget(_label_widget) + hide_studio_overrides_layout.addWidget(hide_studio_overrides) + footer_layout.addWidget(save_as_default_btn, 0) footer_layout.addWidget(refresh_button, 0) + footer_layout.addWidget(hide_studio_overrides_widget, 0) save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() @@ -171,6 +189,11 @@ class SystemWidget(QtWidgets.QWidget): def _on_refresh(self): self.reset() + def _on_hide_studio_overrides(self, state): + self._hide_studio_overrides = (state == QtCore.Qt.Checked) + self._update_values() + self.hierarchical_style_update() + def _save_as_defaults(self): output = {} for item in self.input_fields: @@ -227,7 +250,10 @@ class SystemWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.update_default_values(default_values) - system_values = {"system": studio_system_settings()} + if self._hide_studio_overrides: + system_values = lib.NOT_SET + else: + system_values = {"system": studio_system_settings()} for input_field in self.input_fields: input_field.update_studio_values(system_values) @@ -377,6 +403,7 @@ class ProjectWidget(QtWidgets.QWidget): super(ProjectWidget, self).__init__(parent) self.develop_mode = develop_mode + self._hide_studio_overrides = False self.is_overidable = False self._ignore_value_changes = False @@ -411,8 +438,25 @@ class ProjectWidget(QtWidgets.QWidget): refresh_button.setIcon(refresh_icon) refresh_button.clicked.connect(self._on_refresh) + hide_studio_overrides = QtWidgets.QCheckBox() + hide_studio_overrides.setChecked(self._hide_studio_overrides) + hide_studio_overrides.stateChanged.connect( + self._on_hide_studio_overrides + ) + + hide_studio_overrides_widget = QtWidgets.QWidget() + hide_studio_overrides_layout = QtWidgets.QHBoxLayout( + hide_studio_overrides_widget + ) + _label_widget = QtWidgets.QLabel( + "Hide studio overrides", hide_studio_overrides_widget + ) + hide_studio_overrides_layout.addWidget(_label_widget) + hide_studio_overrides_layout.addWidget(hide_studio_overrides) + footer_layout.addWidget(save_as_default_btn, 0) footer_layout.addWidget(refresh_button, 0) + footer_layout.addWidget(hide_studio_overrides_widget, 0) save_btn = QtWidgets.QPushButton("Save") spacer_widget = QtWidgets.QWidget() @@ -584,6 +628,11 @@ class ProjectWidget(QtWidgets.QWidget): def _on_refresh(self): self.reset() + def _on_hide_studio_overrides(self, state): + self._hide_studio_overrides = (state == QtCore.Qt.Checked) + self._update_values() + self.hierarchical_style_update() + def _save_overrides(self): data = {} for item in self.input_fields: @@ -673,10 +722,13 @@ class ProjectWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.update_default_values(default_values) - studio_values = {"project": { - PROJECT_SETTINGS_KEY: studio_project_settings(), - PROJECT_ANATOMY_KEY: studio_project_anatomy() - }} + if self._hide_studio_overrides: + studio_values = lib.NOT_SET + else: + studio_values = {"project": { + PROJECT_SETTINGS_KEY: studio_project_settings(), + PROJECT_ANATOMY_KEY: studio_project_anatomy() + }} for input_field in self.input_fields: input_field.update_studio_values(studio_values) From 4c54afea72e2f775e781052fbb10345c35aa6090 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 15:19:38 +0200 Subject: [PATCH 930/947] main check for develop arg changed --- pype/tools/settings/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/settings/__main__.py b/pype/tools/settings/__main__.py index 044c2ef495..55a38b3604 100644 --- a/pype/tools/settings/__main__.py +++ b/pype/tools/settings/__main__.py @@ -11,7 +11,7 @@ if __name__ == "__main__": app.setStyleSheet(stylesheet) app.setWindowIcon(QtGui.QIcon(settings.style.app_icon_path())) - develop = "-dev" in sys.argv + develop = "-d" in sys.argv or "--develop" in sys.argv widget = settings.MainWidget(develop) widget.show() From b1a181c6053be65605e1ca370e2679e51371539a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 15 Sep 2020 15:36:25 +0200 Subject: [PATCH 931/947] Do not remove frames if outside of allowed frame ragne --- pype/hosts/harmony/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index d4b7d91fdb..7310e91e9b 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -18,12 +18,7 @@ def set_scene_settings(settings): if (args[0]["frameStart"] && args[0]["frameEnd"]) { var duration = args[0]["frameEnd"] - args[0]["frameStart"] + 1 - if (frame.numberOf() > duration) - { - frame.remove( - duration, frame.numberOf() - duration - ); - } + if (frame.numberOf() < duration) { frame.insert( From 64e84474c99f8b090146cea0eac6f88f66786480 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 15 Sep 2020 17:49:52 +0200 Subject: [PATCH 932/947] psd batch publisher was trying to always publish version 1 --- pype/plugins/global/publish/validate_version.py | 2 +- .../standalonepublisher/publish/collect_context.py | 2 +- .../publish/collect_psd_instances.py | 14 ++------------ 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/pype/plugins/global/publish/validate_version.py b/pype/plugins/global/publish/validate_version.py index 9c7ce72307..6701041541 100644 --- a/pype/plugins/global/publish/validate_version.py +++ b/pype/plugins/global/publish/validate_version.py @@ -10,7 +10,7 @@ class ValidateVersion(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Version" - hosts = ["nuke", "maya", "blender"] + hosts = ["nuke", "maya", "blender", "standalonepublisher"] def process(self, instance): version = instance.data.get("version") diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index a5479fdf13..9dbeec93fb 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -123,7 +123,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "label": subset, "name": subset, "family": in_data["family"], - "version": in_data.get("version", 1), + # "version": in_data.get("version", 1), "frameStart": in_data.get("representations", [None])[0].get( "frameStart", None ), diff --git a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py index 9c8e2eae83..b5db437473 100644 --- a/pype/plugins/standalonepublisher/publish/collect_psd_instances.py +++ b/pype/plugins/standalonepublisher/publish/collect_psd_instances.py @@ -9,7 +9,7 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): """ label = "Collect Psd Instances" - order = pyblish.api.CollectorOrder + 0.492 + order = pyblish.api.CollectorOrder + 0.489 hosts = ["standalonepublisher"] families = ["background_batch"] @@ -34,8 +34,6 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): context = instance.context asset_data = instance.data["assetEntity"] asset_name = instance.data["asset"] - anatomy_data = instance.data["anatomyData"] - for subset_name, subset_data in self.subsets.items(): instance_name = f"{asset_name}_{subset_name}" task = subset_data.get("task", "background") @@ -55,16 +53,8 @@ class CollectPsdInstances(pyblish.api.InstancePlugin): new_instance.data["label"] = f"{instance_name}" new_instance.data["subset"] = subset_name + new_instance.data["task"] = task - # fix anatomy data - anatomy_data_new = copy.deepcopy(anatomy_data) - # updating hierarchy data - anatomy_data_new.update({ - "asset": asset_data["name"], - "task": task, - "subset": subset_name - }) - new_instance.data["anatomyData"] = anatomy_data_new if subset_name in self.unchecked_by_default: new_instance.data["publish"] = False From 35a506b5f4e01f3e7dc959018489344679170bf1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 15 Sep 2020 18:43:51 +0200 Subject: [PATCH 933/947] fixed some alignments --- pype/tools/settings/settings/widgets/item_types.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 51ae0fc985..b79e6f7756 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -608,15 +608,19 @@ class TextWidget(QtWidgets.QWidget, InputObject): self.setFocusProxy(self.text_input) + layout_kwargs = {} + if self.multiline: + layout_kwargs["alignment"] = QtCore.Qt.AlignTop + if not self._as_widget: self.key = input_data["key"] if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) + layout.addWidget(label_widget, 0, **layout_kwargs) self.label_widget = label_widget - layout.addWidget(self.text_input, 1) + layout.addWidget(self.text_input, 1, **layout_kwargs) self.text_input.textChanged.connect(self._on_value_change) @@ -823,9 +827,9 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) - layout.addWidget(label_widget, 0) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget - layout.addWidget(self.text_input, 1) + layout.addWidget(self.text_input, 1, alignment=QtCore.Qt.AlignTop) self.text_input.textChanged.connect(self._on_value_change) From c6b7346a9127712a142d72ae6e888fd4fe6e0224 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 11:58:20 +0200 Subject: [PATCH 934/947] list widget has move up/down btns --- .../settings/settings/widgets/item_types.py | 136 ++++++++++++++++-- 1 file changed, 123 insertions(+), 13 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index b79e6f7756..8257f79fc2 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -9,6 +9,7 @@ from .widgets import ( PathInput ) from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET +from avalon.vendor import qtawesome class SettingObject(AbstractSettingObject): @@ -898,23 +899,46 @@ class ListItem(QtWidgets.QWidget, SettingObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) - self.add_btn = QtWidgets.QPushButton("+") - self.remove_btn = QtWidgets.QPushButton("-") + char_plus = qtawesome.charmap("fa.plus") + char_minus = qtawesome.charmap("fa.minus") + char_up = qtawesome.charmap("fa.angle-up") + char_down = qtawesome.charmap("fa.angle-down") + + self.add_btn = QtWidgets.QPushButton(char_plus) + self.remove_btn = QtWidgets.QPushButton(char_minus) + self.up_btn = QtWidgets.QPushButton(char_up) + self.down_btn = QtWidgets.QPushButton(char_down) + + font_plus_minus = qtawesome.font("fa", 10) + font_up_down = qtawesome.font("fa", 13) + + self.add_btn.setFont(font_plus_minus) + self.remove_btn.setFont(font_plus_minus) + self.up_btn.setFont(font_up_down) + self.down_btn.setFont(font_up_down) self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.up_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.down_btn.setFocusPolicy(QtCore.Qt.ClickFocus) self.add_btn.setFixedSize(self._btn_size, self._btn_size) self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.up_btn.setFixedSize(self._btn_size, self._btn_size) + self.down_btn.setFixedSize(self._btn_size, self._btn_size) self.add_btn.setProperty("btn-type", "tool-item") self.remove_btn.setProperty("btn-type", "tool-item") + self.up_btn.setProperty("btn-type", "tool-item") + self.down_btn.setProperty("btn-type", "tool-item") layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) - self.add_btn.clicked.connect(self.on_add_clicked) - self.remove_btn.clicked.connect(self.on_remove_clicked) + self.add_btn.clicked.connect(self._on_add_clicked) + self.remove_btn.clicked.connect(self._on_remove_clicked) + self.up_btn.clicked.connect(self._on_up_clicked) + self.down_btn.clicked.connect(self._on_down_clicked) ItemKlass = TypeToKlass.types[object_type] self.value_input = ItemKlass( @@ -925,28 +949,62 @@ class ListItem(QtWidgets.QWidget, SettingObject): ) layout.addWidget(self.value_input, 1) + layout.addWidget(self.up_btn, 0) + layout.addWidget(self.down_btn, 0) + self.value_input.value_changed.connect(self._on_value_change) def set_as_empty(self, is_empty=True): self.value_input.setEnabled(not is_empty) self.remove_btn.setEnabled(not is_empty) + self.order_changed() self._on_value_change() + def order_changed(self): + row = self.row() + parent_row_count = self.parent_rows_count() + if parent_row_count == 1: + self.up_btn.setEnabled(False) + self.down_btn.setEnabled(False) + + elif row == 0: + self.up_btn.setEnabled(False) + self.down_btn.setEnabled(True) + + elif row == parent_row_count - 1: + self.up_btn.setEnabled(True) + self.down_btn.setEnabled(False) + + else: + self.up_btn.setEnabled(True) + self.down_btn.setEnabled(True) + def _on_value_change(self, item=None): self.value_changed.emit(self) def row(self): return self._parent.input_fields.index(self) - def on_add_clicked(self): + def parent_rows_count(self): + return len(self._parent.input_fields) + + def _on_add_clicked(self): if self.value_input.isEnabled(): self._parent.add_row(row=self.row() + 1) else: self.set_as_empty(False) - def on_remove_clicked(self): + def _on_remove_clicked(self): self._parent.remove_row(self) + def _on_up_clicked(self): + row = self.row() + self._parent.swap_rows(row - 1, row) + + def _on_down_clicked(self): + row = self.row() + self._parent.swap_rows(row, row + 1) + def config_value(self): if self.value_input.isEnabled(): return self.value_input.item_value() @@ -1044,21 +1102,58 @@ class ListWidget(QtWidgets.QWidget, InputObject): if self.count() == 0: self.add_row(is_empty=True) + def swap_rows(self, row_1, row_2): + if row_1 == row_2: + return + + if row_1 > row_2: + row_1, row_2 = row_2, row_1 + + field_1 = self.input_fields[row_1] + field_2 = self.input_fields[row_2] + + self.input_fields[row_1] = field_2 + self.input_fields[row_2] = field_1 + + layout_index = self.inputs_layout.indexOf(field_1) + self.inputs_layout.insertWidget(layout_index + 1, field_1) + + field_1.order_changed() + field_2.order_changed() + def add_row(self, row=None, value=None, is_empty=False): # Create new item item_widget = ListItem( self.object_type, self.input_modifiers, self, self.inputs_widget ) + if row is None: + if self.input_fields: + self.input_fields[-1].order_changed() + self.inputs_layout.addWidget(item_widget) + self.input_fields.append(item_widget) + else: + previous_field = None + if row > 0: + previous_field = self.input_fields[row - 1] + + next_field = None + max_index = self.count() + if row < max_index: + next_field = self.input_fields[row] + + self.inputs_layout.insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + if previous_field: + previous_field.order_changed() + + if next_field: + next_field.order_changed() + if is_empty: item_widget.set_as_empty() item_widget.value_changed.connect(self._on_value_change) - if row is None: - self.inputs_layout.addWidget(item_widget) - self.input_fields.append(item_widget) - else: - self.inputs_layout.insertWidget(row, item_widget) - self.input_fields.insert(row, item_widget) + item_widget.order_changed() previous_input = None for input_field in self.input_fields: @@ -1085,11 +1180,26 @@ class ListWidget(QtWidgets.QWidget, InputObject): def remove_row(self, item_widget): item_widget.value_changed.disconnect() + row = self.input_fields.index(item_widget) + previous_field = None + next_field = None + if row > 0: + previous_field = self.input_fields[row - 1] + + if row != len(self.input_fields) - 1: + next_field = self.input_fields[row + 1] + self.inputs_layout.removeWidget(item_widget) - self.input_fields.remove(item_widget) + self.input_fields.pop(row) item_widget.setParent(None) item_widget.deleteLater() + if previous_field: + previous_field.order_changed() + + if next_field: + next_field.order_changed() + if self.count() == 0: self.add_row(is_empty=True) From ff2dfec0757aa7974de9f0c49e850eeba35a3180 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 13:37:14 +0200 Subject: [PATCH 935/947] plus minus signe are without awesome font --- pype/tools/settings/settings/widgets/item_types.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 8257f79fc2..ef1e0ad2e1 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -899,21 +899,15 @@ class ListItem(QtWidgets.QWidget, SettingObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) - char_plus = qtawesome.charmap("fa.plus") - char_minus = qtawesome.charmap("fa.minus") char_up = qtawesome.charmap("fa.angle-up") char_down = qtawesome.charmap("fa.angle-down") - self.add_btn = QtWidgets.QPushButton(char_plus) - self.remove_btn = QtWidgets.QPushButton(char_minus) + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") self.up_btn = QtWidgets.QPushButton(char_up) self.down_btn = QtWidgets.QPushButton(char_down) - font_plus_minus = qtawesome.font("fa", 10) font_up_down = qtawesome.font("fa", 13) - - self.add_btn.setFont(font_plus_minus) - self.remove_btn.setFont(font_plus_minus) self.up_btn.setFont(font_up_down) self.down_btn.setFont(font_up_down) From 1f0e5b65dde7bfe366003eca8e983af043fa964f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 13:49:28 +0200 Subject: [PATCH 936/947] labels are at the top --- pype/tools/settings/settings/widgets/item_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index ef1e0ad2e1..06eb82ce74 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -1060,7 +1060,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): if not label_widget: label_widget = QtWidgets.QLabel(input_data["label"], self) - layout.addWidget(label_widget) + layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget @@ -2258,7 +2258,7 @@ class PathWidget(QtWidgets.QWidget, SettingObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - layout.addWidget(label_widget, 0) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) self.label_widget = label_widget self.content_widget = QtWidgets.QWidget(self) From f943b7967949398bc75f5c81d90b12692ae9881f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 14:23:20 +0200 Subject: [PATCH 937/947] abstract methods are implemented directly in SettingObject --- .../settings/settings/widgets/item_types.py | 163 ++++++++++++- .../settings/settings/widgets/widgets.py | 230 ------------------ 2 files changed, 161 insertions(+), 232 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 06eb82ce74..1872d72def 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3,7 +3,6 @@ import logging import collections from Qt import QtWidgets, QtCore, QtGui from .widgets import ( - AbstractSettingObject, ExpandingWidget, NumberSpinBox, PathInput @@ -12,11 +11,31 @@ from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET from avalon.vendor import qtawesome -class SettingObject(AbstractSettingObject): +class SettingObject: is_input_type = True default_input_value = NOT_SET allow_actions = True default_state = "" + abstract_attributes = ("_parent", ) + + def __getattr__(self, name): + if name in self.abstract_attributes: + raise NotImplementedError( + "Attribute `{}` is not implemented. {}".format(name, self) + ) + return super(SettingObject, self).__getattribute__(name) + + @classmethod + def style_state(cls, is_invalid, is_overriden, is_modified): + items = [] + if is_invalid: + items.append("invalid") + else: + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or cls.default_state def set_default_attributes(self): # Default input attributes @@ -245,6 +264,146 @@ class SettingObject(AbstractSettingObject): if item: return item.mouseReleaseEvent(self, event) + def _discard_changes(self): + self.ignore_value_changes = True + self.discard_changes() + self.ignore_value_changes = False + + def discard_changes(self): + raise NotImplementedError( + "{} Method `discard_changes` not implemented!".format( + repr(self) + ) + ) + + def _set_studio_default(self): + self.ignore_value_changes = True + self.set_studio_default() + self.ignore_value_changes = False + + def set_studio_default(self): + raise NotImplementedError( + "{} Method `set_studio_default` not implemented!".format( + repr(self) + ) + ) + + def _reset_to_pype_default(self): + self.ignore_value_changes = True + self.reset_to_pype_default() + self.ignore_value_changes = False + + def reset_to_pype_default(self): + raise NotImplementedError( + "{} Method `reset_to_pype_default` not implemented!".format( + repr(self) + ) + ) + + def _remove_overrides(self): + self.ignore_value_changes = True + self.remove_overrides() + self.ignore_value_changes = False + + def remove_overrides(self): + raise NotImplementedError( + "{} Method `remove_overrides` not implemented!".format( + repr(self) + ) + ) + + def _set_as_overriden(self): + self.ignore_value_changes = True + self.set_as_overriden() + self.ignore_value_changes = False + + def set_as_overriden(self): + raise NotImplementedError( + "{} Method `set_as_overriden` not implemented!".format(repr(self)) + ) + + def hierarchical_style_update(self): + raise NotImplementedError( + "{} Method `hierarchical_style_update` not implemented!".format( + repr(self) + ) + ) + + def update_default_values(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `update_default_values`".format(self) + ) + + def update_studio_values(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `update_studio_values`".format(self) + ) + + def apply_overrides(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `apply_overrides`".format(self) + ) + + @property + def ignore_value_changes(self): + """Most of attribute changes are ignored on value change when True.""" + raise NotImplementedError( + "{} does not have implemented `ignore_value_changes`".format(self) + ) + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + """Setter for global parent item to apply changes for all inputs.""" + raise NotImplementedError(( + "{} does not have implemented setter method `ignore_value_changes`" + ).format(self)) + + @property + def child_has_studio_override(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_has_studio_override`".format( + self + ) + ) + + @property + def child_modified(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_modified`".format(self) + ) + + @property + def child_overriden(self): + """Any children item is overriden.""" + raise NotImplementedError( + "{} does not have implemented `child_overriden`".format(self) + ) + + @property + def child_invalid(self): + """Any children item does not have valid value.""" + raise NotImplementedError( + "{} does not have implemented `child_invalid`".format(self) + ) + + def get_invalid(self): + """Return invalid item types all down the hierarchy.""" + raise NotImplementedError( + "{} does not have implemented `get_invalid`".format(self) + ) + + def item_value(self): + """Value of an item without key.""" + raise NotImplementedError( + "Method `item_value` not implemented!" + ) + + def studio_value(self): + """Output for saving changes or overrides.""" + return {self.key: self.item_value()} + class InputObject(SettingObject): def update_default_values(self, parent_values): diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py index 96f8d2ec17..400b9371fd 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -226,233 +226,3 @@ class UnsavedChangesDialog(QtWidgets.QDialog): def on_discard_pressed(self): self.done(2) - - -class AbstractSettingObject: - abstract_attributes = ("_parent", ) - - def __getattr__(self, name): - if name in self.abstract_attributes: - raise NotImplementedError( - "Attribute `{}` is not implemented. {}".format(name, self) - ) - return super(AbstractSettingObject, self).__getattribute__(name) - - def update_default_values(self, parent_values): - raise NotImplementedError( - "{} does not have implemented `update_default_values`".format(self) - ) - - def update_studio_values(self, parent_values): - raise NotImplementedError( - "{} does not have implemented `update_studio_values`".format(self) - ) - - def apply_overrides(self, parent_values): - raise NotImplementedError( - "{} does not have implemented `apply_overrides`".format(self) - ) - - @property - def is_modified(self): - """Has object any changes that require saving.""" - raise NotImplementedError( - "{} does not have implemented `is_modified`".format(self) - ) - - @property - def is_overriden(self): - """Is object overriden so should be saved to overrides.""" - raise NotImplementedError( - "{} does not have implemented `is_overriden`".format(self) - ) - - @property - def any_parent_is_group(self): - raise NotImplementedError( - "{} does not have implemented `any_parent_is_group`".format(self) - ) - - @property - def was_overriden(self): - """Initial state after applying overrides.""" - raise NotImplementedError( - "{} does not have implemented `was_overriden`".format(self) - ) - - @property - def is_invalid(self): - """Value set in is not valid.""" - raise NotImplementedError( - "{} does not have implemented `is_invalid`".format(self) - ) - - @property - def is_group(self): - """Value set in is not valid.""" - raise NotImplementedError( - "{} does not have implemented `is_group`".format(self) - ) - - @property - def is_nullable(self): - raise NotImplementedError( - "{} does not have implemented `is_nullable`".format(self) - ) - - @property - def is_overidable(self): - """Should care about overrides.""" - raise NotImplementedError( - "{} does not have implemented `is_overidable`".format(self) - ) - - def any_parent_overriden(self): - """Any of parent object up to top hiearchy is overriden.""" - raise NotImplementedError( - "{} does not have implemented `any_parent_overriden`".format(self) - ) - - @property - def ignore_value_changes(self): - """Most of attribute changes are ignored on value change when True.""" - raise NotImplementedError( - "{} does not have implemented `ignore_value_changes`".format(self) - ) - - @ignore_value_changes.setter - def ignore_value_changes(self, value): - """Setter for global parent item to apply changes for all inputs.""" - raise NotImplementedError(( - "{} does not have implemented setter method `ignore_value_changes`" - ).format(self)) - - @property - def child_has_studio_override(self): - """Any children item is modified.""" - raise NotImplementedError( - "{} does not have implemented `child_has_studio_override`".format( - self - ) - ) - - @property - def child_modified(self): - """Any children item is modified.""" - raise NotImplementedError( - "{} does not have implemented `child_modified`".format(self) - ) - - @property - def child_overriden(self): - """Any children item is overriden.""" - raise NotImplementedError( - "{} does not have implemented `child_overriden`".format(self) - ) - - @property - def child_invalid(self): - """Any children item does not have valid value.""" - raise NotImplementedError( - "{} does not have implemented `child_invalid`".format(self) - ) - - def get_invalid(self): - """Returns invalid items all down the hierarchy.""" - raise NotImplementedError( - "{} does not have implemented `get_invalid`".format(self) - ) - - def item_value(self): - """Value of an item without key.""" - raise NotImplementedError( - "Method `item_value` not implemented!" - ) - - def studio_value(self): - """Output for saving changes or overrides.""" - return {self.key: self.item_value()} - - @classmethod - def style_state(cls, is_invalid, is_overriden, is_modified): - items = [] - if is_invalid: - items.append("invalid") - else: - if is_overriden: - items.append("overriden") - if is_modified: - items.append("modified") - return "-".join(items) or cls.default_state - - def add_children_gui(self, child_configuration, values): - raise NotImplementedError( - "{} Method `add_children_gui` is not implemented!.".format( - repr(self) - ) - ) - - def _discard_changes(self): - self.ignore_value_changes = True - self.discard_changes() - self.ignore_value_changes = False - - def discard_changes(self): - raise NotImplementedError( - "{} Method `discard_changes` not implemented!".format( - repr(self) - ) - ) - - def _set_studio_default(self): - self.ignore_value_changes = True - self.set_studio_default() - self.ignore_value_changes = False - - def set_studio_default(self): - raise NotImplementedError( - "{} Method `set_studio_default` not implemented!".format( - repr(self) - ) - ) - - def _reset_to_pype_default(self): - self.ignore_value_changes = True - self.reset_to_pype_default() - self.ignore_value_changes = False - - def reset_to_pype_default(self): - raise NotImplementedError( - "{} Method `reset_to_pype_default` not implemented!".format( - repr(self) - ) - ) - - def _remove_overrides(self): - self.ignore_value_changes = True - self.remove_overrides() - self.ignore_value_changes = False - - def remove_overrides(self): - raise NotImplementedError( - "{} Method `remove_overrides` not implemented!".format( - repr(self) - ) - ) - - def _set_as_overriden(self): - self.ignore_value_changes = True - self.set_as_overriden() - self.ignore_value_changes = False - - def set_as_overriden(self): - raise NotImplementedError( - "{} Method `set_as_overriden` not implemented!".format(repr(self)) - ) - - def hierarchical_style_update(self): - raise NotImplementedError( - "{} Method `hierarchical_style_update` not implemented!".format( - repr(self) - ) - ) From 1e2e278ede49f79f218b964e4dd694f87ca1c69a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Sep 2020 14:25:44 +0200 Subject: [PATCH 938/947] #241 - Implementation of pype-config/presets/plugins/standalonepublisher --- .../projects_schema/1_plugins_gui_schema.json | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json index 6bb14463c1..2e64e6a40b 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -614,6 +614,100 @@ ] } ] + }, + { + "type": "dict", + "collapsable": true, + "key": "standalonepublisher", + "label": "Standalone Publisher", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ExtractThumbnailSP", + "label": "ExtractThumbnailSP", + "is_group": true, + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "ffmpeg_args", + "label": "ffmpeg_args", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + } + ] + } + ] + }, + { + "type": "dict", + "collapsable": true, + "key": "ExtractReviewSP", + "label": "ExtractReviewSP", + "is_group": true, + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "outputs", + "label": "outputs", + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "h264", + "label": "h264", + "children": [ + { + "type": "list", + "object_type": "text", + "key": "input", + "label": "input" + }, + { + "type": "list", + "object_type": "text", + "key": "output", + "label": "output" + }, + { + "type": "list", + "object_type": "text", + "key": "tags", + "label": "tags" + }, + { + "type": "text", + "key": "ext", + "label": "ext" + } + ] + } + ] + } + ] + } + ] + } + ] } ] } From 81df713212f3259a9b60fc76af1e179588892f46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 15:32:59 +0200 Subject: [PATCH 939/947] added few docstrings --- .../settings/settings/widgets/item_types.py | 117 ++++++++++++------ 1 file changed, 80 insertions(+), 37 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 1872d72def..19bfdabb9e 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -12,21 +12,19 @@ from avalon.vendor import qtawesome class SettingObject: + # `is_input_type` attribute says if has implemented item type methods is_input_type = True + # each input must have implemented default value for development + # when defaults are not filled yet default_input_value = NOT_SET + # will allow to show actions for the item type (disabled for proxies) allow_actions = True + # default state of item type default_state = "" - abstract_attributes = ("_parent", ) - - def __getattr__(self, name): - if name in self.abstract_attributes: - raise NotImplementedError( - "Attribute `{}` is not implemented. {}".format(name, self) - ) - return super(SettingObject, self).__getattribute__(name) @classmethod def style_state(cls, is_invalid, is_overriden, is_modified): + """Return stylesheet state by intered booleans.""" items = [] if is_invalid: items.append("invalid") @@ -37,7 +35,11 @@ class SettingObject: items.append("modified") return "-".join(items) or cls.default_state - def set_default_attributes(self): + def _set_default_attributes(self): + """Create and reset attributes required for all item types. + + They may not be used in the item but are required to be set. + """ # Default input attributes self._has_studio_override = False self._had_studio_override = False @@ -73,7 +75,12 @@ class SettingObject: self.defaults_not_set = False def initial_attributes(self, input_data, parent, as_widget): - self.set_default_attributes() + """Prepare attributes based on entered arguments. + + This method should be same for each item type. Few item types + may require to extend with specific attributes for their case. + """ + self._set_default_attributes() self._parent = parent self._as_widget = as_widget @@ -90,24 +97,70 @@ class SettingObject: @property def develop_mode(self): + """Tool is in develop mode or not. + + Returns: + bool + + """ return self._parent.develop_mode @property def log(self): + """Auto created logger for debugging.""" if self._log is None: self._log = logging.getLogger(self.__class__.__name__) return self._log - @property - def has_studio_override(self): - return self._has_studio_override or self._parent.has_studio_override - @property def had_studio_override(self): + """Item had studio overrides on refresh. + + Returns: + bool + + """ return self._had_studio_override + @property + def has_studio_override(self): + """Item has studio override at the moment. + + With combination of `had_studio_override` is possible to know if item + has changes (not just value change). + + Returns: + bool + + """ + return self._has_studio_override or self._parent.has_studio_override + + @property + def is_group(self): + """Item represents key that can be overriden. + + Attribute `is_group` can be set to True only once in item hierarchy. + + Returns: + bool + + """ + return self._is_group + @property def any_parent_is_group(self): + """Any parent of item is group. + + Attribute holding this information is set during creation and + stored to `_any_parent_is_group`. + + Why is this information useful: If any parent is group and + the parent is set as overriden, this item is overriden too. + + Returns: + bool + + """ if self._any_parent_is_group is None: return super(SettingObject, self).any_parent_is_group return self._any_parent_is_group @@ -130,7 +183,7 @@ class SettingObject: @property def was_overriden(self): - """Initial state after applying overrides.""" + """Item had set value of project overrides on project change.""" if self._as_widget: return self._parent.was_overriden return self._was_overriden @@ -140,13 +193,12 @@ class SettingObject: """Value set in is not valid.""" return self._is_invalid - @property - def is_group(self): - """Value set in is not valid.""" - return self._is_group - @property def is_nullable(self): + """Value of item can be set to None. + + NOT IMPLEMENTED! + """ return self._is_nullable @property @@ -155,7 +207,12 @@ class SettingObject: return self._parent.is_overidable def any_parent_overriden(self): - """Any of parent object up to top hiearchy is overriden.""" + """Any of parent objects up to top hiearchy item is overriden. + + Returns: + bool + + """ if self._parent._is_overriden: return True return self._parent.any_parent_overriden() @@ -344,20 +401,6 @@ class SettingObject: "{} does not have implemented `apply_overrides`".format(self) ) - @property - def ignore_value_changes(self): - """Most of attribute changes are ignored on value change when True.""" - raise NotImplementedError( - "{} does not have implemented `ignore_value_changes`".format(self) - ) - - @ignore_value_changes.setter - def ignore_value_changes(self, value): - """Setter for global parent item to apply changes for all inputs.""" - raise NotImplementedError(( - "{} does not have implemented setter method `ignore_value_changes`" - ).format(self)) - @property def child_has_studio_override(self): """Any children item is modified.""" @@ -1049,7 +1092,7 @@ class ListItem(QtWidgets.QWidget, SettingObject): def __init__(self, object_type, input_modifiers, config_parent, parent): super(ListItem, self).__init__(parent) - self.set_default_attributes() + self._set_default_attributes() self._parent = config_parent self._any_parent_is_group = True @@ -1430,7 +1473,7 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): def __init__(self, object_type, input_modifiers, config_parent, parent): super(ModifiableDictItem, self).__init__(parent) - self.set_default_attributes() + self._set_default_attributes() self._parent = config_parent self.is_key_duplicated = False From 1273e7c6ef9367302a77b50c2198b87b3bba84f1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 15:36:26 +0200 Subject: [PATCH 940/947] fixed typo --- pype/tools/settings/settings/widgets/item_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 19bfdabb9e..e2d59c2e69 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -284,7 +284,7 @@ class SettingObject: and not self.any_parent_is_group and not self._had_studio_override ): - action = QtWidgets.QAction("Set sudio default") + action = QtWidgets.QAction("Set studio default") actions_mapping[action] = self._set_studio_default menu.addAction(action) From 9049554abf7970fa8cff8ca111096ffcb8fb4fa9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Sep 2020 16:42:43 +0200 Subject: [PATCH 941/947] #241 - Removed ExtractReviewSP element --- .../projects_schema/1_plugins_gui_schema.json | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json index 2e64e6a40b..96aad3e5a9 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -642,39 +642,7 @@ "label": "ffmpeg_args", "children": [ { - "type": "list", - "object_type": "text", - "key": "input", - "label": "input" - }, - { - "type": "list", - "object_type": "text", - "key": "output", - "label": "output" - } - ] - } - ] - }, - { - "type": "dict", - "collapsable": true, - "key": "ExtractReviewSP", - "label": "ExtractReviewSP", - "is_group": true, - "children": [ - { - "type": "dict", - "collapsable": false, - "key": "outputs", - "label": "outputs", - "children": [ - { - "type": "dict", - "collapsable": false, - "key": "h264", - "label": "h264", + "type": "dict-form", "children": [ { "type": "list", @@ -687,17 +655,6 @@ "object_type": "text", "key": "output", "label": "output" - }, - { - "type": "list", - "object_type": "text", - "key": "tags", - "label": "tags" - }, - { - "type": "text", - "key": "ext", - "label": "ext" } ] } From 923c00d646b91450ce25d8ff121c9dd2c61b549c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 16 Sep 2020 17:20:22 +0200 Subject: [PATCH 942/947] #241 - Implemented pype-config/presets/plugins/nukestudio/filter.json --- .../projects_schema/1_plugins_gui_schema.json | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json index 96aad3e5a9..0c3b2c25d2 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -531,6 +531,55 @@ "key": "nukestudio", "label": "NukeStudio", "children": [ + { + "type": "dict", + "collapsable": true, + "key": "filter", + "label": "Filter", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "strict", + "label": "strict", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "ValidateVersion", + "label": "ValidateVersion" + }, + { + "type": "boolean", + "key": "VersionUpWorkfile", + "label": "VersionUpWorkfile" + } + ] + }, + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "benevolent", + "label": "benevolent", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "ValidateVersion", + "label": "ValidateVersion" + }, + { + "type": "boolean", + "key": "VersionUpWorkfile", + "label": "VersionUpWorkfile" + } + ] + } + ] + }, { "type": "dict", "collapsable": true, From 3c0fc416d45cc990fd7eabdf5a62eaeef4fecf74 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 17:44:22 +0200 Subject: [PATCH 943/947] removed duplicated method --- pype/tools/settings/settings/widgets/item_types.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index e2d59c2e69..24c22e754a 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -22,19 +22,6 @@ class SettingObject: # default state of item type default_state = "" - @classmethod - def style_state(cls, is_invalid, is_overriden, is_modified): - """Return stylesheet state by intered booleans.""" - items = [] - if is_invalid: - items.append("invalid") - else: - if is_overriden: - items.append("overriden") - if is_modified: - items.append("modified") - return "-".join(items) or cls.default_state - def _set_default_attributes(self): """Create and reset attributes required for all item types. From 737d02ec9ed37123a7abcecfff5b2ae27a6d3aca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 17:44:40 +0200 Subject: [PATCH 944/947] added docstrings to SettingObject's methods --- .../settings/settings/widgets/item_types.py | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 24c22e754a..9388d5e124 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -103,6 +103,9 @@ class SettingObject: def had_studio_override(self): """Item had studio overrides on refresh. + Use attribute `_had_studio_override` which should be changed only + during methods `update_studio_values` and `update_default_values`. + Returns: bool @@ -114,7 +117,7 @@ class SettingObject: """Item has studio override at the moment. With combination of `had_studio_override` is possible to know if item - has changes (not just value change). + is modified (not value change). Returns: bool @@ -190,7 +193,8 @@ class SettingObject: @property def is_overidable(self): - """Should care about overrides.""" + """ care about overrides.""" + return self._parent.is_overidable def any_parent_overriden(self): @@ -200,6 +204,7 @@ class SettingObject: bool """ + if self._parent._is_overriden: return True return self._parent.any_parent_overriden() @@ -222,6 +227,7 @@ class SettingObject: def style_state( cls, has_studio_override, is_invalid, is_overriden, is_modified ): + """Return stylesheet state by intered booleans.""" items = [] if is_invalid: items.append("invalid") @@ -314,6 +320,15 @@ class SettingObject: self.ignore_value_changes = False def discard_changes(self): + """Item's implementation to discard all changes made by user. + + Reset all values to same values as had when opened GUI + or when changed project. + + Must not affect `had_studio_override` value or `was_overriden` + value. It must be marked that there are keys/values which are not in + defaults or overrides. + """ raise NotImplementedError( "{} Method `discard_changes` not implemented!".format( repr(self) @@ -326,6 +341,10 @@ class SettingObject: self.ignore_value_changes = False def set_studio_default(self): + """Item's implementation to set current values as studio's overrides. + + Mark item and it's children as they have studio overrides. + """ raise NotImplementedError( "{} Method `set_studio_default` not implemented!".format( repr(self) @@ -338,6 +357,11 @@ class SettingObject: self.ignore_value_changes = False def reset_to_pype_default(self): + """Item's implementation to remove studio overrides. + + Mark item as it does not have studio overrides unset studio + override values. + """ raise NotImplementedError( "{} Method `reset_to_pype_default` not implemented!".format( repr(self) @@ -350,6 +374,11 @@ class SettingObject: self.ignore_value_changes = False def remove_overrides(self): + """Item's implementation to remove project overrides. + + Mark item as does not have project overrides. Must not change + `was_overriden` attribute value. + """ raise NotImplementedError( "{} Method `remove_overrides` not implemented!".format( repr(self) @@ -362,11 +391,21 @@ class SettingObject: self.ignore_value_changes = False def set_as_overriden(self): + """Item's implementation to set values as overriden for project. + + Mark item and all it's children as they're overriden. Must skip + items with children items that has attributes `is_group` + and `any_parent_is_group` set to False. In that case those items + are not meant to be overridable and should trigger the method on it's + children. + + """ raise NotImplementedError( "{} Method `set_as_overriden` not implemented!".format(repr(self)) ) def hierarchical_style_update(self): + """Trigger update style method down the hierarchy.""" raise NotImplementedError( "{} Method `hierarchical_style_update` not implemented!".format( repr(self) @@ -374,23 +413,51 @@ class SettingObject: ) def update_default_values(self, parent_values): + """Fill default values on startup or on refresh. + + Default values stored in `pype` repository should update all items in + schema. Each item should take values for his key and set it's value or + pass values down to children items. + + Args: + parent_values (dict): Values of parent's item. But in case item is + used as widget, `parent_values` contain value for item. + """ raise NotImplementedError( "{} does not have implemented `update_default_values`".format(self) ) def update_studio_values(self, parent_values): + """Fill studio override values on startup or on refresh. + + Set studio value if is not set to NOT_SET, in that case studio + overrides are not set yet. + + Args: + parent_values (dict): Values of parent's item. But in case item is + used as widget, `parent_values` contain value for item. + """ raise NotImplementedError( "{} does not have implemented `update_studio_values`".format(self) ) def apply_overrides(self, parent_values): + """Fill project override values on startup, refresh or project change. + + Set project value if is not set to NOT_SET, in that case project + overrides are not set yet. + + Args: + parent_values (dict): Values of parent's item. But in case item is + used as widget, `parent_values` contain value for item. + """ raise NotImplementedError( "{} does not have implemented `apply_overrides`".format(self) ) @property def child_has_studio_override(self): - """Any children item is modified.""" + """Any children item has studio overrides.""" raise NotImplementedError( "{} does not have implemented `child_has_studio_override`".format( self @@ -406,7 +473,7 @@ class SettingObject: @property def child_overriden(self): - """Any children item is overriden.""" + """Any children item has project overrides.""" raise NotImplementedError( "{} does not have implemented `child_overriden`".format(self) ) From b74375325fc9656331287796a017068267168d65 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 18:22:39 +0200 Subject: [PATCH 945/947] default_state removed --- pype/tools/settings/settings/widgets/item_types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 9388d5e124..d78d335902 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -19,8 +19,6 @@ class SettingObject: default_input_value = NOT_SET # will allow to show actions for the item type (disabled for proxies) allow_actions = True - # default state of item type - default_state = "" def _set_default_attributes(self): """Create and reset attributes required for all item types. @@ -240,7 +238,7 @@ class SettingObject: if not items and has_studio_override: items.append("studio") - return "-".join(items) or cls.default_state + return "-".join(items) or "" def mouseReleaseEvent(self, event): if self.allow_actions and event.button() == QtCore.Qt.RightButton: From a8e03ee091e7a311a0a866a9c13ab7275a07b235 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 16 Sep 2020 18:25:00 +0200 Subject: [PATCH 946/947] few more docstrings --- .../settings/settings/widgets/item_types.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index d78d335902..ea32d9c79c 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -12,13 +12,18 @@ from avalon.vendor import qtawesome class SettingObject: + """Partially abstract class for Setting's item type workflow.""" # `is_input_type` attribute says if has implemented item type methods is_input_type = True - # each input must have implemented default value for development - # when defaults are not filled yet + # Each input must have implemented default value for development + # when defaults are not filled yet. default_input_value = NOT_SET - # will allow to show actions for the item type (disabled for proxies) + # Will allow to show actions for the item type (disabled for proxies) else + # item is skipped and try to trigger actions on it's parent. allow_actions = True + # All item types must have implemented Qt signal which is emitted when + # it's or it's children value has changed, + value_changed = None def _set_default_attributes(self): """Create and reset attributes required for all item types. @@ -501,6 +506,11 @@ class SettingObject: class InputObject(SettingObject): + """Class for inputs with pre-implemented methods. + + Class is for item types not creating or using other item types, most + of methods has same code in that case. + """ def update_default_values(self, parent_values): self._state = None self._is_modified = False From 733ae9d8b1130c584d74345c7631d69717e16757 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 17 Sep 2020 10:26:06 +0200 Subject: [PATCH 947/947] #241 - Reverted checkboxes back to raw-json Both keys and values for this field are editable, no valid widget for such a case --- .../projects_schema/1_plugins_gui_schema.json | 48 ++----------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json index 0c3b2c25d2..721b0924e8 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -532,53 +532,11 @@ "label": "NukeStudio", "children": [ { - "type": "dict", + "type": "raw-json", "collapsable": true, "key": "filter", - "label": "Filter", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "strict", - "label": "strict", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "ValidateVersion", - "label": "ValidateVersion" - }, - { - "type": "boolean", - "key": "VersionUpWorkfile", - "label": "VersionUpWorkfile" - } - ] - }, - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "benevolent", - "label": "benevolent", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "ValidateVersion", - "label": "ValidateVersion" - }, - { - "type": "boolean", - "key": "VersionUpWorkfile", - "label": "VersionUpWorkfile" - } - ] - } - ] + "label": "Publish GUI Filters", + "is_file": true }, { "type": "dict",