diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..f821749b08 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,20 @@ +version: 2.1 + +jobs: + deploy-website: + docker: + - image: circleci/node:10.16 + + steps: + - checkout + - run: + name: Deploying to GitHub Pages + command: | + git config --global user.email "mkolar@users.noreply.github.com" + git config --global user.name "Website Deployment Script" + echo "machine github.com login mkolar password $GITHUB_TOKEN" > ~/.netrc + cd website && yarn install && GIT_USER=mkolar yarn run deploy + +workflows: + build_and_deploy: + jobs: diff --git a/pype/api.py b/pype/api.py index 021080b4d5..c1bf84b4ef 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,6 +1,7 @@ from .settings import ( system_settings, - project_settings + project_settings, + environments ) from pypeapp import ( Logger, @@ -55,6 +56,7 @@ from .lib import _subprocess as subprocess __all__ = [ "system_settings", "project_settings", + "environments", "Logger", "Anatomy", diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index 1ffc6d8b6d..a5820a352f 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -130,9 +130,11 @@ def check_inventory(): def application_launch(): - """Set scene settings and check outdated containers on Harmony startup.""" - ensure_scene_settings() - check_inventory() + # FIXME: This is breaking server <-> client communication. + # It is now moved so it it manually called. + # ensure_scene_settings() + # check_inventory() + pass def export_template(backdrops, nodes, filepath): diff --git a/pype/lib.py b/pype/lib.py index 6fa204b379..afcfa98307 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -82,41 +82,67 @@ def get_ffmpeg_tool_path(tool="ffmpeg"): # Special naming case for subprocess since its a built-in method. def _subprocess(*args, **kwargs): - """Convenience method for getting output errors for subprocess.""" + """Convenience method for getting output errors for subprocess. - # make sure environment contains only strings - if not kwargs.get("env"): - filtered_env = {k: str(v) for k, v in os.environ.items()} - else: - filtered_env = {k: str(v) for k, v in kwargs.get("env").items()} + Entered arguments and keyword arguments are passed to subprocess Popen. + + Args: + *args: Variable length arument list passed to Popen. + **kwargs : Arbitary keyword arguments passed to Popen. Is possible to + pass `logging.Logger` object under "logger" if want to use + different than lib's logger. + + Returns: + str: Full output of subprocess concatenated stdout and stderr. + + Raises: + RuntimeError: Exception is raised if process finished with nonzero + return code. + """ + + # Get environents from kwarg or use current process environments if were + # not passed. + env = kwargs.get("env") or os.environ + # Make sure environment contains only strings + filtered_env = {k: str(v) for k, v in env.items()} + + # Use lib's logger if was not passed with kwargs. + logger = kwargs.pop("logger", log) # set overrides kwargs['stdout'] = kwargs.get('stdout', subprocess.PIPE) - kwargs['stderr'] = kwargs.get('stderr', subprocess.STDOUT) + kwargs['stderr'] = kwargs.get('stderr', subprocess.PIPE) kwargs['stdin'] = kwargs.get('stdin', subprocess.PIPE) kwargs['env'] = filtered_env proc = subprocess.Popen(*args, **kwargs) - output, error = proc.communicate() + full_output = "" + _stdout, _stderr = proc.communicate() + if _stdout: + _stdout = _stdout.decode("utf-8") + full_output += _stdout + logger.debug(_stdout) - if output: - output = output.decode("utf-8") - output += "\n" - for line in output.strip().split("\n"): - log.info(line) - - if error: - error = error.decode("utf-8") - error += "\n" - for line in error.strip().split("\n"): - log.error(line) + if _stderr: + _stderr = _stderr.decode("utf-8") + # Add additional line break if output already containt stdout + if full_output: + full_output += "\n" + full_output += _stderr + logger.warning(_stderr) if proc.returncode != 0: - raise ValueError( - "\"{}\" was not successful:\nOutput: {}\nError: {}".format( - args, output, error)) - return output + exc_msg = "Executing arguments was not successful: \"{}\"".format(args) + if _stdout: + exc_msg += "\n\nOutput:\n{}".format(_stdout) + + if _stderr: + exc_msg += "Error:\n{}".format(_stderr) + + raise RuntimeError(exc_msg) + + return full_output def get_hierarchy(asset_name=None): @@ -1408,41 +1434,76 @@ def source_hash(filepath, *args): return "|".join([file_name, time, size] + list(args)).replace(".", ",") -def get_latest_version(asset_name, subset_name): +def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): """Retrieve latest version from `asset_name`, and `subset_name`. + Do not use if you want to query more than 5 latest versions as this method + query 3 times to mongo for each call. For those cases is better to use + more efficient way, e.g. with help of aggregations. + Args: asset_name (str): Name of asset. subset_name (str): Name of subset. + dbcon (avalon.mongodb.AvalonMongoDB, optional): Avalon Mongo connection + with Session. + project_name (str, optional): Find latest version in specific project. + + Returns: + None: If asset, subset or version were not found. + dict: Last version document for entered . """ - # Get asset - asset_name = io.find_one( - {"type": "asset", "name": asset_name}, projection={"name": True} + + if not dbcon: + log.debug("Using `avalon.io` for query.") + dbcon = io + # Make sure is installed + io.install() + + if project_name and project_name != dbcon.Session.get("AVALON_PROJECT"): + # `avalon.io` has only `_database` attribute + # but `AvalonMongoDB` has `database` + database = getattr(dbcon, "database", dbcon._database) + collection = database[project_name] + else: + project_name = dbcon.Session.get("AVALON_PROJECT") + collection = dbcon + + log.debug(( + "Getting latest version for Project: \"{}\" Asset: \"{}\"" + " and Subset: \"{}\"" + ).format(project_name, asset_name, subset_name)) + + # Query asset document id by asset name + asset_doc = collection.find_one( + {"type": "asset", "name": asset_name}, + {"_id": True} ) + if not asset_doc: + log.info( + "Asset \"{}\" was not found in Database.".format(asset_name) + ) + return None - subset = io.find_one( - {"type": "subset", "name": subset_name, "parent": asset_name["_id"]}, - projection={"_id": True, "name": True}, + subset_doc = collection.find_one( + {"type": "subset", "name": subset_name, "parent": asset_doc["_id"]}, + {"_id": True} ) + if not subset_doc: + log.info( + "Subset \"{}\" was not found in Database.".format(subset_name) + ) + return None - # 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, + version_doc = collection.find_one( + {"type": "version", "parent": subset_doc["_id"]}, sort=[("name", -1)], ) - - assert version, "No version found, this is a bug" - - return version + if not version_doc: + log.info( + "Subset \"{}\" does not have any version yet.".format(subset_name) + ) + return None + return version_doc class ApplicationLaunchFailed(Exception): diff --git a/pype/modules/__init__.py b/pype/modules/__init__.py index e69de29bb2..aacd541e18 100644 --- a/pype/modules/__init__.py +++ b/pype/modules/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from .base import PypeModule + +__all__ = ( + "PypeModule", +) diff --git a/pype/modules/base.py b/pype/modules/base.py new file mode 100644 index 0000000000..ee90aa4cbb --- /dev/null +++ b/pype/modules/base.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +"""Base class for Pype Modules.""" +from uuid import uuid4 +from abc import ABC, abstractmethod +from pype.api import Logger + + +class PypeModule(ABC): + """Base class of pype module. + + Attributes: + id (UUID): Module id. + enabled (bool): Is module enabled. + name (str): Module name. + """ + + enabled = False + name = None + _id = None + + def __init__(self, settings): + if self.name is None: + self.name = self.__class__.__name__ + + self.log = Logger().get_logger(self.name) + + self.settings = settings.get(self.name) + self.enabled = settings.get("enabled", False) + self._id = uuid4() + + @property + def id(self): + return self._id + + @abstractmethod + def startup_environments(self): + """Get startup environments for module.""" + return {} diff --git a/pype/modules_manager.py b/pype/modules_manager.py new file mode 100644 index 0000000000..6538187ea9 --- /dev/null +++ b/pype/modules_manager.py @@ -0,0 +1,102 @@ +import os +import inspect + +import pype.modules +from pype.modules import PypeModule +from pype.settings import system_settings +from pype.api import Logger + + +class PypeModuleManager: + skip_module_names = ("__pycache__", ) + + def __init__(self): + self.log = Logger().get_logger( + "{}.{}".format(__name__, self.__class__.__name__) + ) + + self.pype_modules = self.find_pype_modules() + + def modules_environments(self): + environments = {} + for pype_module in self.pype_modules.values(): + environments.update(pype_module.startup_environments()) + return environments + + def find_pype_modules(self): + settings = system_settings() + modules = [] + dirpath = os.path.dirname(pype.modules.__file__) + for module_name in os.listdir(dirpath): + # Check if path lead to a folder + full_path = os.path.join(dirpath, module_name) + if not os.path.isdir(full_path): + continue + + # Skip known invalid names + if module_name in self.skip_module_names: + continue + + import_name = "pype.modules.{}".format(module_name) + try: + modules.append( + __import__(import_name, fromlist=[""]) + ) + + except Exception: + self.log.warning( + "Couldn't import {}".format(import_name), exc_info=True + ) + + pype_module_classes = [] + for module in modules: + try: + pype_module_classes.extend( + self._classes_from_module(PypeModule, module) + ) + except Exception: + self.log.warning( + "Couldn't import {}".format(import_name), exc_info=True + ) + + pype_modules = {} + for pype_module_class in pype_module_classes: + try: + pype_module = pype_module_class(settings) + if pype_module.enabled: + pype_modules[pype_module.id] = pype_module + except Exception: + self.log.warning( + "Couldn't create instance of {}".format( + pype_module_class.__class__.__name__ + ), + exc_info=True + ) + return pype_modules + + def _classes_from_module(self, superclass, module): + classes = list() + + def recursive_bases(klass): + output = [] + output.extend(klass.__bases__) + for base in klass.__bases__: + output.extend(recursive_bases(base)) + return output + + for name in dir(module): + # It could be anything at this point + obj = getattr(module, name) + + if not inspect.isclass(obj) or not len(obj.__bases__) > 0: + continue + + # Use string comparison rather than `issubclass` + # in order to support reloading of this module. + bases = recursive_bases(obj) + if not any(base.__name__ == superclass.__name__ for base in bases): + continue + + classes.append(obj) + + return classes diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index 549dc22d79..93a07a9fae 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -1,5 +1,6 @@ import pyblish.api import json +import os class IntegrateFtrackInstance(pyblish.api.InstancePlugin): @@ -68,6 +69,16 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "name": "thumbnail" # Default component name is "main". } comp['thumbnail'] = True + comp_files = comp["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + comp['published_path'] = os.path.join( + comp['stagingDir'], filename + ) + elif comp.get('ftrackreview') or ("ftrackreview" in comp.get('tags', [])): ''' Ftrack bug requirement: diff --git a/pype/plugins/global/publish/cleanup.py b/pype/plugins/global/publish/cleanup.py index 264a04b8bd..5fded85ccb 100644 --- a/pype/plugins/global/publish/cleanup.py +++ b/pype/plugins/global/publish/cleanup.py @@ -21,6 +21,7 @@ class CleanUp(pyblish.api.InstancePlugin): # Presets paterns = None # list of regex paterns + remove_temp_renders = True def process(self, instance): """Plugin entry point.""" @@ -36,8 +37,9 @@ class CleanUp(pyblish.api.InstancePlugin): ) ) - self.log.info("Cleaning renders new...") - self.clean_renders(instance) + if self.remove_temp_renders: + self.log.info("Cleaning renders new...") + self.clean_renders(instance) if [ef for ef in self.exclude_families if instance.data["family"] in ef]: @@ -85,7 +87,11 @@ class CleanUp(pyblish.api.InstancePlugin): 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) + try: + os.remove(src) + except PermissionError: + self.log.warning("Insufficient permission to delete {}".format(src)) + continue # add dir for cleanup dirnames.append(os.path.dirname(src)) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 353f2f27f0..bf29e12eab 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -74,12 +74,10 @@ class ExtractBurnin(pype.api.Extractor): # Remove any representations tagged for deletion. # QUESTION Is possible to have representation with "delete" tag? for repre in tuple(instance.data["representations"]): - if "delete" in repre.get("tags", []): + if all(x in repre.get("tags", []) for x in ['delete', 'burnin']): self.log.debug("Removing representation: {}".format(repre)) instance.data["representations"].remove(repre) - self.log.debug(instance.data["representations"]) - def use_legacy_code(self, instance): presets = instance.context.data.get("presets") if presets is None and self.profiles is None: @@ -229,8 +227,7 @@ class ExtractBurnin(pype.api.Extractor): self.log.debug("Executing: {}".format(args)) # Run burnin script - output = pype.api.subprocess(args, shell=True) - self.log.debug("Output: {}".format(output)) + pype.api.subprocess(args, shell=True, logger=self.log) for filepath in temp_data["full_input_paths"]: filepath = filepath.replace("\\", "/") diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index f4a39a7c31..06b9d177f1 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -51,6 +51,7 @@ class ExtractReview(pyblish.api.InstancePlugin): to_height = 1080 def process(self, instance): + self.log.debug(instance.data["representations"]) # Skip review when requested. if not instance.data.get("review", True): return @@ -77,7 +78,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # Make sure cleanup happens and pop representations with "delete" tag. for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] - if "delete" in tags: + if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) def main_process(self, instance): @@ -180,8 +181,10 @@ class ExtractReview(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - output = pype.api.subprocess(subprcs_cmd, shell=True) - self.log.debug("Output: {}".format(output)) + + pype.api.subprocess( + subprcs_cmd, shell=True, logger=self.log + ) output_name = output_def["filename_suffix"] if temp_data["without_handles"]: diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index fd109cf881..dcc87188f3 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -4,7 +4,8 @@ import os import json import re -from copy import copy +from copy import copy, deepcopy +import pype.api from avalon import api, io from avalon.vendor import requests, clique @@ -42,40 +43,6 @@ def _get_script(path): return str(path) -def get_latest_version(asset_name, subset_name, family): - """Retrieve latest files concerning extendFrame feature.""" - # 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 (pre-run check) - assert subset, "No subsets found, please publish with `extendFrames` off" - - # Get version - version_projection = { - "name": True, - "data.startFrame": True, - "data.endFrame": True, - "parent": True, - } - - version = io.find_one( - {"type": "version", "parent": subset["_id"], "data.families": family}, - projection=version_projection, - sort=[("name", -1)], - ) - - assert version, "No version found, this is a bug" - - return version - - def get_resources(version, extension=None): """Get the files from the specific version.""" query = {"type": "representation", "parent": version["_id"]} @@ -250,7 +217,19 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): subset = data["subset"] job_name = "Publish - {subset}".format(subset=subset) - output_dir = instance.data["outputDir"] + # instance.data.get("subset") != instances[0]["subset"] + # 'Main' vs 'renderMain' + override_version = None + instance_version = instance.data.get("version") # take this if exists + if instance_version != 1: + override_version = instance_version + output_dir = self._get_publish_folder(instance.context.data['anatomy'], + deepcopy( + instance.data["anatomyData"]), + instance.data.get("asset"), + instances[0]["subset"], + 'render', + override_version) # Generate the payload for Deadline submission payload = { @@ -322,7 +301,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): payload["JobInfo"].pop("SecondaryPool", None) self.log.info("Submitting Deadline job ...") - # self.log.info(json.dumps(payload, indent=4, sort_keys=True)) url = "{}/api/jobs".format(self.DEADLINE_REST_URL) response = requests.post(url, json=payload, timeout=10) @@ -349,9 +327,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # get latest version of subset # this will stop if subset wasn't published yet - version = get_latest_version( - instance.data.get("asset"), - instance.data.get("subset"), "render") + version = pype.api.get_latest_version(instance.data.get("asset"), + instance.data.get("subset")) # get its files based on extension subset_resources = get_resources(version, representation.get("ext")) r_col, _ = clique.assemble(subset_resources) @@ -741,7 +718,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "families": []}) # skip locking version if we are creating v01 - instance_version = instance.data.get("version") + instance_version = instance.data.get("version") # take this if exists if instance_version != 1: instance_skeleton_data["version"] = instance_version @@ -997,11 +974,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): prev_start = None prev_end = None - version = get_latest_version( - asset_name=asset, - subset_name=subset, - family='render' - ) + version = pype.api.get_latest_version(asset_name=asset, + subset_name=subset + ) # Set prev start / end frames for comparison if not prev_start and not prev_end: @@ -1017,3 +992,58 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ) return updated_start, updated_end + + def _get_publish_folder(self, anatomy, template_data, + asset, subset, + family='render', version=None): + """ + Extracted logic to pre-calculate real publish folder, which is + calculated in IntegrateNew inside of Deadline process. + This should match logic in: + 'collect_anatomy_instance_data' - to + get correct anatomy, family, version for subset and + 'collect_resources_path' + get publish_path + + Args: + anatomy (pypeapp.lib.anatomy.Anatomy): + template_data (dict): pre-calculated collected data for process + asset (string): asset name + subset (string): subset name (actually group name of subset) + family (string): for current deadline process it's always 'render' + TODO - for generic use family needs to be dynamically + calculated like IntegrateNew does + version (int): override version from instance if exists + + Returns: + (string): publish folder where rendered and published files will + be stored + based on 'publish' template + """ + if not version: + version = pype.api.get_latest_version(asset, subset) + if version: + version = int(version["name"]) + 1 + + template_data["subset"] = subset + template_data["family"] = "render" + template_data["version"] = version + + anatomy_filled = anatomy.format(template_data) + + if "folder" in anatomy.templates["publish"]: + publish_folder = anatomy_filled["publish"]["folder"] + else: + # solve deprecated situation when `folder` key is not underneath + # `publish` anatomy + project_name = api.Session["AVALON_PROJECT"] + self.log.warning(( + "Deprecation warning: Anatomy does not have set `folder`" + " key underneath `publish` (in global of for project `{}`)." + ).format(project_name)) + + file_path = anatomy_filled["publish"]["path"] + # Directory + publish_folder = os.path.dirname(file_path) + + return publish_folder diff --git a/pype/plugins/hiero/publish/collect_clips.py b/pype/plugins/hiero/publish/collect_clips.py index 2c7ea3ec60..724e4730ed 100644 --- a/pype/plugins/hiero/publish/collect_clips.py +++ b/pype/plugins/hiero/publish/collect_clips.py @@ -143,8 +143,8 @@ class CollectClips(api.ContextPlugin): "asset": asset, "family": "clip", "families": [], - "handleStart": projectdata.get("handleStart", 0), - "handleEnd": projectdata.get("handleEnd", 0), + "handleStart": int(projectdata.get("handleStart", 0)), + "handleEnd": int(projectdata.get("handleEnd", 0)), "fps": context.data["fps"] }) instance = context.create_instance(**data) diff --git a/pype/plugins/hiero/publish/collect_reviews.py b/pype/plugins/hiero/publish/collect_review.py similarity index 71% rename from pype/plugins/hiero/publish/collect_reviews.py rename to pype/plugins/hiero/publish/collect_review.py index a444d57d6b..f1767b2a68 100644 --- a/pype/plugins/hiero/publish/collect_reviews.py +++ b/pype/plugins/hiero/publish/collect_review.py @@ -1,8 +1,10 @@ from pyblish import api import os +import re +import clique -class CollectReviews(api.InstancePlugin): +class CollectReview(api.InstancePlugin): """Collect review from tags. Tag is expected to have metadata: @@ -14,11 +16,13 @@ class CollectReviews(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1022 - label = "Collect Reviews" + label = "Collect Review" hosts = ["hiero"] families = ["plate"] def process(self, instance): + is_sequence = instance.data["isSequence"] + # Exclude non-tagged instances. tagged = False for tag in instance.data["tags"]: @@ -83,7 +87,29 @@ class CollectReviews(api.InstancePlugin): file_path = rev_inst.data.get("sourcePath") file_dir = os.path.dirname(file_path) file = os.path.basename(file_path) - ext = os.path.splitext(file)[-1][1:] + ext = os.path.splitext(file)[-1] + + # detect if sequence + if not is_sequence: + # is video file + files = file + else: + files = list() + source_first = instance.data["sourceFirst"] + self.log.debug("_ file: {}".format(file)) + spliter, padding = self.detect_sequence(file) + self.log.debug("_ spliter, padding: {}, {}".format( + spliter, padding)) + base_name = file.split(spliter)[0] + collection = clique.Collection(base_name, ext, padding, set(range( + int(source_first + rev_inst.data.get("sourceInH")), + int(source_first + rev_inst.data.get("sourceOutH") + 1)))) + self.log.debug("_ collection: {}".format(collection)) + real_files = os.listdir(file_dir) + for item in collection: + if item not in real_files: + continue + files.append(item) # change label instance.data["label"] = "{0} - {1} - ({2})".format( @@ -94,7 +120,7 @@ class CollectReviews(api.InstancePlugin): # adding representation for review mov representation = { - "files": file, + "files": files, "stagingDir": file_dir, "frameStart": rev_inst.data.get("sourceIn"), "frameEnd": rev_inst.data.get("sourceOut"), @@ -102,9 +128,9 @@ class CollectReviews(api.InstancePlugin): "frameEndFtrack": rev_inst.data.get("sourceOutH"), "step": 1, "fps": rev_inst.data.get("fps"), - "name": "preview", - "tags": ["preview", "ftrackreview"], - "ext": ext + "name": "review", + "tags": ["review", "ftrackreview"], + "ext": ext[1:] } media_duration = instance.data.get("mediaDuration") @@ -136,7 +162,12 @@ class CollectReviews(api.InstancePlugin): source_path = instance.data["sourcePath"] source_file = os.path.basename(source_path) - head, ext = os.path.splitext(source_file) + spliter, padding = self.detect_sequence(source_file) + + if spliter: + head, ext = source_file.split(spliter) + else: + head, ext = os.path.splitext(source_file) # staging dir creation staging_dir = os.path.dirname( @@ -144,30 +175,28 @@ class CollectReviews(api.InstancePlugin): media_duration = instance.data.get("mediaDuration") clip_duration_h = instance.data.get("clipDurationH") + self.log.debug("__ media_duration: {}".format(media_duration)) + self.log.debug("__ clip_duration_h: {}".format(clip_duration_h)) - if media_duration > clip_duration_h: - thumb_frame = instance.data["clipInH"] + ( - (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) - elif media_duration <= clip_duration_h: - thumb_frame = instance.data["sourceIn"] + ( - (instance.data["sourceOut"] - instance.data["sourceIn"]) / 2) - thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") + thumb_frame = int(instance.data["sourceIn"] + ( + (instance.data["sourceOut"] - instance.data["sourceIn"]) / 2)) + + thumb_file = "{}thumbnail{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) self.log.debug("__ thumb_path: {}".format(thumb_path)) self.log.debug("__ thumb_frame: {}".format(thumb_frame)) + self.log.debug( + "__ sourceIn: `{}`".format(instance.data["sourceIn"])) + thumbnail = item.thumbnail(thumb_frame).save( thumb_path, format='png' ) - - self.log.debug( - "__ sourceIn: `{}`".format(instance.data["sourceIn"])) self.log.debug( "__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) self.log.debug("__ thumbnail: {}".format(thumbnail)) - thumb_representation = { 'files': thumb_file, 'stagingDir': staging_dir, @@ -205,3 +234,26 @@ class CollectReviews(api.InstancePlugin): instance.data["versionData"] = version_data instance.data["source"] = instance.data["sourcePath"] + + def detect_sequence(self, file): + """ Get identificating pater for image sequence + + Can find file.0001.ext, file.%02d.ext, file.####.ext + + Return: + string: any matching sequence patern + int: padding of sequnce numbering + """ + foundall = re.findall( + r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file) + if foundall: + found = sorted(list(set(foundall[0])))[-1] + + if "%" in found: + padding = int(re.findall(r"\d+", found)[-1]) + else: + padding = len(found) + + return found, padding + else: + return None, None diff --git a/pype/plugins/hiero/publish/extract_review_cutup.py b/pype/plugins/hiero/publish/extract_review_cutup.py new file mode 100644 index 0000000000..57ec6c1107 --- /dev/null +++ b/pype/plugins/hiero/publish/extract_review_cutup.py @@ -0,0 +1,325 @@ +import os +import sys +import six +import errno +from pyblish import api +import pype +import clique +from avalon.vendor import filelink + + +class ExtractReviewCutUp(pype.api.Extractor): + """Cut up clips from long video file""" + + order = api.ExtractorOrder + # order = api.CollectorOrder + 0.1023 + label = "Extract Review CutUp" + hosts = ["hiero"] + families = ["review"] + + # presets + tags_addition = [] + + def process(self, instance): + inst_data = instance.data + asset = inst_data['asset'] + + # get representation and loop them + representations = inst_data["representations"] + + # check if sequence + is_sequence = inst_data["isSequence"] + + # get resolution default + resolution_width = inst_data["resolutionWidth"] + resolution_height = inst_data["resolutionHeight"] + + # frame range data + media_duration = inst_data["mediaDuration"] + + ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") + + # filter out mov and img sequences + representations_new = representations[:] + for repre in representations: + input_args = list() + output_args = list() + + tags = repre.get("tags", []) + + # check if supported tags are in representation for activation + filter_tag = False + for tag in ["_cut-bigger", "_cut-smaller"]: + if tag in tags: + filter_tag = True + break + if not filter_tag: + continue + + self.log.debug("__ repre: {}".format(repre)) + + files = repre.get("files") + staging_dir = repre.get("stagingDir") + fps = repre.get("fps") + ext = repre.get("ext") + + # make paths + full_output_dir = os.path.join( + staging_dir, "cuts") + + if is_sequence: + new_files = list() + + # frame range delivery included handles + frame_start = ( + inst_data["frameStart"] - inst_data["handleStart"]) + frame_end = ( + inst_data["frameEnd"] + inst_data["handleEnd"]) + self.log.debug("_ frame_start: {}".format(frame_start)) + self.log.debug("_ frame_end: {}".format(frame_end)) + + # make collection from input files list + collections, remainder = clique.assemble(files) + collection = collections.pop() + self.log.debug("_ collection: {}".format(collection)) + + # name components + head = collection.format("{head}") + padding = collection.format("{padding}") + tail = collection.format("{tail}") + self.log.debug("_ head: {}".format(head)) + self.log.debug("_ padding: {}".format(padding)) + self.log.debug("_ tail: {}".format(tail)) + + # make destination file with instance data + # frame start and end range + index = 0 + for image in collection: + dst_file_num = frame_start + index + dst_file_name = head + str(padding % dst_file_num) + tail + src = os.path.join(staging_dir, image) + dst = os.path.join(full_output_dir, dst_file_name) + self.log.info("Creating temp hardlinks: {}".format(dst)) + self.hardlink_file(src, dst) + new_files.append(dst_file_name) + index += 1 + + self.log.debug("_ new_files: {}".format(new_files)) + + else: + # ffmpeg when single file + new_files = "{}_{}".format(asset, files) + + # frame range + frame_start = repre.get("frameStart") + frame_end = repre.get("frameEnd") + + full_input_path = os.path.join( + staging_dir, files) + + os.path.isdir(full_output_dir) or os.makedirs(full_output_dir) + + full_output_path = os.path.join( + full_output_dir, new_files) + + self.log.debug( + "__ full_input_path: {}".format(full_input_path)) + self.log.debug( + "__ full_output_path: {}".format(full_output_path)) + + # check if audio stream is in input video file + ffprob_cmd = ( + "{ffprobe_path} -i \"{full_input_path}\" -show_streams " + "-select_streams a -loglevel error" + ).format(**locals()) + self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) + audio_check_output = pype.api.subprocess(ffprob_cmd) + self.log.debug( + "audio_check_output: {}".format(audio_check_output)) + + # translate frame to sec + start_sec = float(frame_start) / fps + duration_sec = float(frame_end - frame_start + 1) / fps + + empty_add = None + + # check if not missing frames at start + if (start_sec < 0) or (media_duration < frame_end): + # for later swithing off `-c:v copy` output arg + empty_add = True + + # init empty variables + video_empty_start = video_layer_start = "" + audio_empty_start = audio_layer_start = "" + video_empty_end = video_layer_end = "" + audio_empty_end = audio_layer_end = "" + audio_input = audio_output = "" + v_inp_idx = 0 + concat_n = 1 + + # try to get video native resolution data + try: + resolution_output = pype.api.subprocess(( + "{ffprobe_path} -i \"{full_input_path}\" -v error " + "-select_streams v:0 -show_entries " + "stream=width,height -of csv=s=x:p=0" + ).format(**locals())) + + x, y = resolution_output.split("x") + resolution_width = int(x) + resolution_height = int(y) + except Exception as _ex: + self.log.warning( + "Video native resolution is untracable: {}".format( + _ex)) + + if audio_check_output: + # adding input for empty audio + input_args.append("-f lavfi -i anullsrc") + + # define audio empty concat variables + audio_input = "[1:a]" + audio_output = ":a=1" + v_inp_idx = 1 + + # adding input for video black frame + input_args.append(( + "-f lavfi -i \"color=c=black:" + "s={resolution_width}x{resolution_height}:r={fps}\"" + ).format(**locals())) + + if (start_sec < 0): + # recalculate input video timing + empty_start_dur = abs(start_sec) + start_sec = 0 + duration_sec = float(frame_end - ( + frame_start + (empty_start_dur * fps)) + 1) / fps + + # define starting empty video concat variables + video_empty_start = ( + "[{v_inp_idx}]trim=duration={empty_start_dur}[gv0];" # noqa + ).format(**locals()) + video_layer_start = "[gv0]" + + if audio_check_output: + # define starting empty audio concat variables + audio_empty_start = ( + "[0]atrim=duration={empty_start_dur}[ga0];" + ).format(**locals()) + audio_layer_start = "[ga0]" + + # alter concat number of clips + concat_n += 1 + + # check if not missing frames at the end + if (media_duration < frame_end): + # recalculate timing + empty_end_dur = float( + frame_end - media_duration + 1) / fps + duration_sec = float( + media_duration - frame_start) / fps + + # define ending empty video concat variables + video_empty_end = ( + "[{v_inp_idx}]trim=duration={empty_end_dur}[gv1];" + ).format(**locals()) + video_layer_end = "[gv1]" + + if audio_check_output: + # define ending empty audio concat variables + audio_empty_end = ( + "[0]atrim=duration={empty_end_dur}[ga1];" + ).format(**locals()) + audio_layer_end = "[ga0]" + + # alter concat number of clips + concat_n += 1 + + # concatting black frame togather + output_args.append(( + "-filter_complex \"" + "{audio_empty_start}" + "{video_empty_start}" + "{audio_empty_end}" + "{video_empty_end}" + "{video_layer_start}{audio_layer_start}[1:v]{audio_input}" # noqa + "{video_layer_end}{audio_layer_end}" + "concat=n={concat_n}:v=1{audio_output}\"" + ).format(**locals())) + + # append ffmpeg input video clip + input_args.append("-ss {:0.2f}".format(start_sec)) + input_args.append("-t {:0.2f}".format(duration_sec)) + input_args.append("-i \"{}\"".format(full_input_path)) + + # add copy audio video codec if only shortening clip + if ("_cut-bigger" in tags) and (not empty_add): + output_args.append("-c:v copy") + + # make sure it is having no frame to frame comprassion + output_args.append("-intra") + + # output filename + output_args.append("-y \"{}\"".format(full_output_path)) + + 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)) + + repre_new = { + "files": new_files, + "stagingDir": full_output_dir, + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartFtrack": frame_start, + "frameEndFtrack": frame_end, + "step": 1, + "fps": fps, + "name": "cut_up_preview", + "tags": ["review"] + self.tags_addition, + "ext": ext, + "anatomy_template": "publish" + } + + representations_new.append(repre_new) + + for repre in representations_new: + if ("delete" in repre.get("tags", [])) and ( + "cut_up_preview" not in repre["name"]): + representations_new.remove(repre) + + self.log.debug( + "Representations: {}".format(representations_new)) + instance.data["representations"] = representations_new + + def hardlink_file(self, src, dst): + dirname = os.path.dirname(dst) + + # make sure the destination folder exist + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) + + # create hardlined file + try: + filelink.create(src, dst, filelink.HARDLINK) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) diff --git a/pype/plugins/hiero/publish/extract_review_cutup_video.py b/pype/plugins/hiero/publish/extract_review_cutup_video.py deleted file mode 100644 index 868d450fd6..0000000000 --- a/pype/plugins/hiero/publish/extract_review_cutup_video.py +++ /dev/null @@ -1,244 +0,0 @@ -import os -from pyblish import api -import pype - - -class ExtractReviewCutUpVideo(pype.api.Extractor): - """Cut up clips from long video file""" - - order = api.ExtractorOrder - # order = api.CollectorOrder + 0.1023 - label = "Extract Review CutUp Video" - hosts = ["hiero"] - families = ["review"] - - # presets - tags_addition = [] - - def process(self, instance): - inst_data = instance.data - asset = inst_data['asset'] - - # get representation and loop them - representations = inst_data["representations"] - - # get resolution default - resolution_width = inst_data["resolutionWidth"] - resolution_height = inst_data["resolutionHeight"] - - # frame range data - media_duration = inst_data["mediaDuration"] - - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") - ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") - - # filter out mov and img sequences - representations_new = representations[:] - for repre in representations: - input_args = list() - output_args = list() - - tags = repre.get("tags", []) - - # check if supported tags are in representation for activation - filter_tag = False - for tag in ["_cut-bigger", "_cut-smaller"]: - if tag in tags: - filter_tag = True - break - if not filter_tag: - continue - - self.log.debug("__ repre: {}".format(repre)) - - file = repre.get("files") - staging_dir = repre.get("stagingDir") - frame_start = repre.get("frameStart") - frame_end = repre.get("frameEnd") - fps = repre.get("fps") - ext = repre.get("ext") - - new_file_name = "{}_{}".format(asset, file) - - full_input_path = os.path.join( - staging_dir, file) - - full_output_dir = os.path.join( - staging_dir, "cuts") - - os.path.isdir(full_output_dir) or os.makedirs(full_output_dir) - - full_output_path = os.path.join( - full_output_dir, new_file_name) - - self.log.debug("__ full_input_path: {}".format(full_input_path)) - self.log.debug("__ full_output_path: {}".format(full_output_path)) - - # check if audio stream is in input video file - ffprob_cmd = ( - "{ffprobe_path} -i \"{full_input_path}\" -show_streams " - "-select_streams a -loglevel error" - ).format(**locals()) - self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) - audio_check_output = pype.api.subprocess(ffprob_cmd) - self.log.debug("audio_check_output: {}".format(audio_check_output)) - - # translate frame to sec - start_sec = float(frame_start) / fps - duration_sec = float(frame_end - frame_start + 1) / fps - - empty_add = None - - # check if not missing frames at start - if (start_sec < 0) or (media_duration < frame_end): - # for later swithing off `-c:v copy` output arg - empty_add = True - - # init empty variables - video_empty_start = video_layer_start = "" - audio_empty_start = audio_layer_start = "" - video_empty_end = video_layer_end = "" - audio_empty_end = audio_layer_end = "" - audio_input = audio_output = "" - v_inp_idx = 0 - concat_n = 1 - - # try to get video native resolution data - try: - resolution_output = pype.api.subprocess(( - "{ffprobe_path} -i \"{full_input_path}\" -v error " - "-select_streams v:0 -show_entries " - "stream=width,height -of csv=s=x:p=0" - ).format(**locals())) - - x, y = resolution_output.split("x") - resolution_width = int(x) - resolution_height = int(y) - except Exception as E: - self.log.warning( - "Video native resolution is untracable: {}".format(E)) - - if audio_check_output: - # adding input for empty audio - input_args.append("-f lavfi -i anullsrc") - - # define audio empty concat variables - audio_input = "[1:a]" - audio_output = ":a=1" - v_inp_idx = 1 - - # adding input for video black frame - input_args.append(( - "-f lavfi -i \"color=c=black:" - "s={resolution_width}x{resolution_height}:r={fps}\"" - ).format(**locals())) - - if (start_sec < 0): - # recalculate input video timing - empty_start_dur = abs(start_sec) - start_sec = 0 - duration_sec = float(frame_end - ( - frame_start + (empty_start_dur * fps)) + 1) / fps - - # define starting empty video concat variables - video_empty_start = ( - "[{v_inp_idx}]trim=duration={empty_start_dur}[gv0];" - ).format(**locals()) - video_layer_start = "[gv0]" - - if audio_check_output: - # define starting empty audio concat variables - audio_empty_start = ( - "[0]atrim=duration={empty_start_dur}[ga0];" - ).format(**locals()) - audio_layer_start = "[ga0]" - - # alter concat number of clips - concat_n += 1 - - # check if not missing frames at the end - if (media_duration < frame_end): - # recalculate timing - empty_end_dur = float(frame_end - media_duration + 1) / fps - duration_sec = float(media_duration - frame_start) / fps - - # define ending empty video concat variables - video_empty_end = ( - "[{v_inp_idx}]trim=duration={empty_end_dur}[gv1];" - ).format(**locals()) - video_layer_end = "[gv1]" - - if audio_check_output: - # define ending empty audio concat variables - audio_empty_end = ( - "[0]atrim=duration={empty_end_dur}[ga1];" - ).format(**locals()) - audio_layer_end = "[ga0]" - - # alter concat number of clips - concat_n += 1 - - # concatting black frame togather - output_args.append(( - "-filter_complex \"" - "{audio_empty_start}" - "{video_empty_start}" - "{audio_empty_end}" - "{video_empty_end}" - "{video_layer_start}{audio_layer_start}[1:v]{audio_input}" - "{video_layer_end}{audio_layer_end}" - "concat=n={concat_n}:v=1{audio_output}\"" - ).format(**locals())) - - # append ffmpeg input video clip - input_args.append("-ss {:0.2f}".format(start_sec)) - input_args.append("-t {:0.2f}".format(duration_sec)) - input_args.append("-i \"{}\"".format(full_input_path)) - - # add copy audio video codec if only shortening clip - if ("_cut-bigger" in tags) and (not empty_add): - output_args.append("-c:v copy") - - # make sure it is having no frame to frame comprassion - output_args.append("-intra") - - # output filename - output_args.append("-y \"{}\"".format(full_output_path)) - - 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)) - - repre_new = { - "files": new_file_name, - "stagingDir": full_output_dir, - "frameStart": frame_start, - "frameEnd": frame_end, - "frameStartFtrack": frame_start, - "frameEndFtrack": frame_end, - "step": 1, - "fps": fps, - "name": "cut_up_preview", - "tags": ["review"] + self.tags_addition, - "ext": ext, - "anatomy_template": "publish" - } - - representations_new.append(repre_new) - - for repre in representations_new: - if ("delete" in repre.get("tags", [])) and ( - "cut_up_preview" not in repre["name"]): - representations_new.remove(repre) - - self.log.debug( - "Representations: {}".format(representations_new)) - instance.data["representations"] = representations_new diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index c5ce288540..44b9cb4a34 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -119,13 +119,14 @@ class LoadSequence(api.Loader): repr_cont = context["representation"]["context"] if "#" not in file: frame = repr_cont.get("frame") - padding = len(frame) - file = file.replace(frame, "#" * padding) + if frame: + padding = len(frame) + file = file.replace(frame, "#" * padding) read_name = "Read_{0}_{1}_{2}".format( repr_cont["asset"], repr_cont["subset"], - repr_cont["representation"]) + context["representation"]["name"]) # Create the Loader with the filename path set with viewer_update_and_undo_stop(): @@ -249,8 +250,9 @@ class LoadSequence(api.Loader): if "#" not in file: frame = repr_cont.get("frame") - padding = len(frame) - file = file.replace(frame, "#" * padding) + if frame: + padding = len(frame) + file = file.replace(frame, "#" * padding) # Get start frame from version data version = io.find_one({ diff --git a/pype/plugins/nuke/publish/collect_review.py b/pype/plugins/nuke/publish/collect_review.py index e7e8da19a1..42aa910917 100644 --- a/pype/plugins/nuke/publish/collect_review.py +++ b/pype/plugins/nuke/publish/collect_review.py @@ -26,20 +26,24 @@ 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"]} + # * Add audio to instance if exists. + # Find latest versions document + version_doc = pype.api.get_latest_version( + instance.context.data["assetEntity"]["name"], "audioMain" + ) + repre_doc = None + if version_doc: + # Try to find it's representation (Expected there is only one) + repre_doc = io.find_one( + {"type": "representation", "parent": version_doc["_id"]} ) + + # Add audio to instance if representation was found + if repre_doc: instance.data["audio"] = [{ "offset": 0, - "filename": api.get_representation_path(representation) + "filename": api.get_representation_path(repre_doc) }] - except AssertionError: - pass instance.data["families"].append("review") instance.data['families'].append('ftrack') diff --git a/pype/plugins/standalonepublisher/publish/collect_representation_names.py b/pype/plugins/standalonepublisher/publish/collect_representation_names.py new file mode 100644 index 0000000000..c9063c22ed --- /dev/null +++ b/pype/plugins/standalonepublisher/publish/collect_representation_names.py @@ -0,0 +1,31 @@ +import re +import os +import pyblish.api + + +class CollectRepresentationNames(pyblish.api.InstancePlugin): + """ + Sets the representation names for given families based on RegEx filter + """ + + label = "Collect Representaion Names" + order = pyblish.api.CollectorOrder + families = [] + hosts = ["standalonepublisher"] + name_filter = "" + + def process(self, instance): + for repre in instance.data['representations']: + new_repre_name = None + if isinstance(repre['files'], list): + shortened_name = os.path.splitext(repre['files'][0])[0] + new_repre_name = re.search(self.name_filter, + shortened_name).group() + else: + new_repre_name = re.search(self.name_filter, + repre['files']).group() + + if new_repre_name: + repre['name'] = new_repre_name + + repre['outputName'] = repre['name'] diff --git a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py index 5882775083..fca4039d0e 100644 --- a/pype/plugins/standalonepublisher/publish/extract_thumbnail.py +++ b/pype/plugins/standalonepublisher/publish/extract_thumbnail.py @@ -112,12 +112,11 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin): 'ext': 'jpg', 'files': filename, "stagingDir": staging_dir, - "thumbnail": True, - "tags": [] + "tags": ["thumbnail"], } # # add Delete tag when temp file was rendered - # if not is_jpeg: - # representation["tags"].append("delete") + if not is_jpeg: + representation["tags"].append("delete") instance.data["representations"].append(representation) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index 6607726c73..99611b172c 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -4,18 +4,16 @@ import re import subprocess import json import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins -from pype.api import Logger, config +from pype.api import config import pype.lib -log = Logger().get_logger("BurninWrapper", "burninwrap") - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") FFMPEG = ( - '{} -loglevel panic -i "%(input)s" %(filters)s %(args)s%(output)s' + '{} -i "%(input)s" %(filters)s %(args)s%(output)s' ).format(ffmpeg_path) FFPROBE = ( @@ -54,7 +52,7 @@ def _streams(source): def get_fps(str_value): if str_value == "0/0": - log.warning("Source has \"r_frame_rate\" value set to \"0/0\".") + print("WARNING: Source has \"r_frame_rate\" value set to \"0/0\".") return "Unknown" items = str_value.split("/") @@ -299,17 +297,34 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): args=args, overwrite=overwrite ) - log.info("Launching command: {}".format(command)) + print("Launching command: {}".format(command)) + + proc = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True + ) + + _stdout, _stderr = proc.communicate() + if _stdout: + print(_stdout.decode("utf-8")) + + # This will probably never happen as ffmpeg use stdout + if _stderr: + print(_stderr.decode("utf-8")) - proc = subprocess.Popen(command, shell=True) - log.info(proc.communicate()[0]) if proc.returncode != 0: - raise RuntimeError("Failed to render '%s': %s'" - % (output, command)) + raise RuntimeError( + "Failed to render '{}': {}'".format(output, command) + ) if is_sequence: output = output % kwargs.get("duration") + if not os.path.exists(output): - raise RuntimeError("Failed to generate this fucking file '%s'" % output) + raise RuntimeError( + "Failed to generate this f*cking file '%s'" % output + ) def example(input_path, output_path): @@ -542,6 +557,7 @@ def burnins_from_data( if __name__ == "__main__": + print("* Burnin script started") in_data = json.loads(sys.argv[-1]) burnins_from_data( in_data["input"], @@ -551,3 +567,4 @@ if __name__ == "__main__": options=in_data.get("options"), burnin_values=in_data.get("values") ) + print("* Burnin script has finished") diff --git a/pype/settings/__init__.py b/pype/settings/__init__.py index 7e73d541a4..7a99ba0b2f 100644 --- a/pype/settings/__init__.py +++ b/pype/settings/__init__.py @@ -1,9 +1,11 @@ from .lib import ( system_settings, - project_settings + project_settings, + environments ) __all__ = ( "system_settings", - "project_settings" + "project_settings", + "environments" ) diff --git a/pype/settings/defaults/system_settings/environments/avalon.json b/pype/settings/defaults/environments/avalon.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/avalon.json rename to pype/settings/defaults/environments/avalon.json diff --git a/pype/settings/defaults/system_settings/environments/blender.json b/pype/settings/defaults/environments/blender.json similarity index 82% rename from pype/settings/defaults/system_settings/environments/blender.json rename to pype/settings/defaults/environments/blender.json index 6f4f6a012d..00a4070b8e 100644 --- a/pype/settings/defaults/system_settings/environments/blender.json +++ b/pype/settings/defaults/environments/blender.json @@ -3,5 +3,6 @@ "PYTHONPATH": [ "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", "{PYTHONPATH}" - ] + ], + "CREATE_NEW_CONSOLE": "yes" } diff --git a/pype/settings/defaults/system_settings/environments/celaction.json b/pype/settings/defaults/environments/celaction.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/celaction.json rename to pype/settings/defaults/environments/celaction.json diff --git a/pype/settings/defaults/system_settings/environments/deadline.json b/pype/settings/defaults/environments/deadline.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/deadline.json rename to pype/settings/defaults/environments/deadline.json diff --git a/pype/settings/defaults/system_settings/environments/ftrack.json b/pype/settings/defaults/environments/ftrack.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/ftrack.json rename to pype/settings/defaults/environments/ftrack.json diff --git a/pype/settings/defaults/system_settings/environments/global.json b/pype/settings/defaults/environments/global.json similarity index 69% rename from pype/settings/defaults/system_settings/environments/global.json rename to pype/settings/defaults/environments/global.json index ef528e6857..717e337db8 100644 --- a/pype/settings/defaults/system_settings/environments/global.json +++ b/pype/settings/defaults/environments/global.json @@ -6,21 +6,9 @@ "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" + "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/windows/bin", + "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/darwin/bin", + "linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/linux" }, "PATH": [ "{PYPE_CONFIG}/launchers", diff --git a/pype/settings/defaults/system_settings/environments/harmony.json b/pype/settings/defaults/environments/harmony.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/harmony.json rename to pype/settings/defaults/environments/harmony.json diff --git a/pype/settings/defaults/system_settings/environments/houdini.json b/pype/settings/defaults/environments/houdini.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/houdini.json rename to pype/settings/defaults/environments/houdini.json diff --git a/pype/settings/defaults/system_settings/environments/maya.json b/pype/settings/defaults/environments/maya.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/maya.json rename to pype/settings/defaults/environments/maya.json diff --git a/pype/settings/defaults/system_settings/environments/maya_2018.json b/pype/settings/defaults/environments/maya_2018.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/maya_2018.json rename to pype/settings/defaults/environments/maya_2018.json diff --git a/pype/settings/defaults/system_settings/environments/maya_2020.json b/pype/settings/defaults/environments/maya_2020.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/maya_2020.json rename to pype/settings/defaults/environments/maya_2020.json diff --git a/pype/settings/defaults/system_settings/environments/mayabatch.json b/pype/settings/defaults/environments/mayabatch.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/mayabatch.json rename to pype/settings/defaults/environments/mayabatch.json diff --git a/pype/settings/defaults/system_settings/environments/mayabatch_2019.json b/pype/settings/defaults/environments/mayabatch_2019.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/mayabatch_2019.json rename to pype/settings/defaults/environments/mayabatch_2019.json diff --git a/pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json b/pype/settings/defaults/environments/mtoa_3.1.1.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json rename to pype/settings/defaults/environments/mtoa_3.1.1.json diff --git a/pype/settings/defaults/system_settings/environments/muster.json b/pype/settings/defaults/environments/muster.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/muster.json rename to pype/settings/defaults/environments/muster.json diff --git a/pype/settings/defaults/system_settings/environments/nuke.json b/pype/settings/defaults/environments/nuke.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nuke.json rename to pype/settings/defaults/environments/nuke.json diff --git a/pype/settings/defaults/system_settings/environments/nukestudio.json b/pype/settings/defaults/environments/nukestudio.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukestudio.json rename to pype/settings/defaults/environments/nukestudio.json diff --git a/pype/settings/defaults/system_settings/environments/nukestudio_10.0.json b/pype/settings/defaults/environments/nukestudio_10.0.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukestudio_10.0.json rename to pype/settings/defaults/environments/nukestudio_10.0.json diff --git a/pype/settings/defaults/system_settings/environments/nukex.json b/pype/settings/defaults/environments/nukex.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukex.json rename to pype/settings/defaults/environments/nukex.json diff --git a/pype/settings/defaults/system_settings/environments/nukex_10.0.json b/pype/settings/defaults/environments/nukex_10.0.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/nukex_10.0.json rename to pype/settings/defaults/environments/nukex_10.0.json diff --git a/pype/settings/defaults/environments/photoshop.json b/pype/settings/defaults/environments/photoshop.json new file mode 100644 index 0000000000..d39634ce20 --- /dev/null +++ b/pype/settings/defaults/environments/photoshop.json @@ -0,0 +1,7 @@ +{ + "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", + "PYTHONPATH": "{PYTHONPATH}", + "PYPE_LOG_NO_COLORS": "Yes", + "WEBSOCKET_URL": "ws://localhost:8099/ws/", + "WORKFILES_SAVE_AS": "Yes" +} diff --git a/pype/settings/defaults/system_settings/environments/premiere.json b/pype/settings/defaults/environments/premiere.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/premiere.json rename to pype/settings/defaults/environments/premiere.json diff --git a/pype/settings/defaults/system_settings/environments/resolve.json b/pype/settings/defaults/environments/resolve.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/resolve.json rename to pype/settings/defaults/environments/resolve.json diff --git a/pype/settings/defaults/system_settings/environments/storyboardpro.json b/pype/settings/defaults/environments/storyboardpro.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/storyboardpro.json rename to pype/settings/defaults/environments/storyboardpro.json diff --git a/pype/settings/defaults/system_settings/environments/unreal_4.24.json b/pype/settings/defaults/environments/unreal_4.24.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/unreal_4.24.json rename to pype/settings/defaults/environments/unreal_4.24.json diff --git a/pype/settings/defaults/system_settings/environments/vray_4300.json b/pype/settings/defaults/environments/vray_4300.json similarity index 100% rename from pype/settings/defaults/system_settings/environments/vray_4300.json rename to pype/settings/defaults/environments/vray_4300.json diff --git a/pype/settings/defaults/launchers/blender_2.80.toml b/pype/settings/defaults/launchers/blender_2.80.toml new file mode 100644 index 0000000000..88b5ea0c11 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.80.toml @@ -0,0 +1,8 @@ +application_dir = "blender" +executable = "blender_2.80" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.80" +ftrack_label = "Blender" +icon = "app_icons/blender.png" +ftrack_icon = "{}/app_icons/blender.png" diff --git a/pype/settings/defaults/launchers/blender_2.81.toml b/pype/settings/defaults/launchers/blender_2.81.toml new file mode 100644 index 0000000000..072eaa8141 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.81.toml @@ -0,0 +1,9 @@ +application_dir = "blender" +executable = "blender_2.81" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.81" +icon = "app_icons/blender.png" + +ftrack_label = "Blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/launchers/blender_2.82.toml b/pype/settings/defaults/launchers/blender_2.82.toml new file mode 100644 index 0000000000..a485f790f1 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.82.toml @@ -0,0 +1,9 @@ +application_dir = "blender" +executable = "blender_2.82" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.82" +icon = "app_icons/blender.png" + +ftrack_label = "Blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/launchers/blender_2.83.toml b/pype/settings/defaults/launchers/blender_2.83.toml new file mode 100644 index 0000000000..0f98151d01 --- /dev/null +++ b/pype/settings/defaults/launchers/blender_2.83.toml @@ -0,0 +1,9 @@ +application_dir = "blender" +executable = "blender_2.83" +schema = "avalon-core:application-1.0" +label = "Blender" +label_variant = "2.83" +icon = "app_icons/blender.png" + +ftrack_label = "Blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/launchers/celaction_local.toml b/pype/settings/defaults/launchers/celaction_local.toml new file mode 100644 index 0000000000..6cc5d4fa0e --- /dev/null +++ b/pype/settings/defaults/launchers/celaction_local.toml @@ -0,0 +1,9 @@ +executable = "celaction_local" +schema = "avalon-core:application-1.0" +application_dir = "celaction" +label = "CelAction2D" +icon = "app_icons/celaction_local.png" +launch_hook = "pype/hooks/celaction/prelaunch.py/CelactionPrelaunchHook" + +ftrack_label = "CelAction2D" +ftrack_icon = '{}/app_icons/celaction_local.png' diff --git a/pype/settings/defaults/launchers/celaction_publish.toml b/pype/settings/defaults/launchers/celaction_publish.toml new file mode 100644 index 0000000000..dc7ac82673 --- /dev/null +++ b/pype/settings/defaults/launchers/celaction_publish.toml @@ -0,0 +1,8 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "celaction_publish" +label = "Celaction Shell" +icon = "app_icons/celaction.png" + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/launchers/darwin/blender_2.82 b/pype/settings/defaults/launchers/darwin/blender_2.82 new file mode 100644 index 0000000000..8254411ea2 --- /dev/null +++ b/pype/settings/defaults/launchers/darwin/blender_2.82 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open -a blender $@ diff --git a/pype/settings/defaults/launchers/darwin/harmony_17 b/pype/settings/defaults/launchers/darwin/harmony_17 new file mode 100644 index 0000000000..b7eba2c2d0 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/darwin/harmony_17_launch b/pype/settings/defaults/launchers/darwin/harmony_17_launch new file mode 100644 index 0000000000..5dcf5db57e --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/darwin/python3 b/pype/settings/defaults/launchers/darwin/python3 new file mode 100644 index 0000000000..c2b82c7638 --- /dev/null +++ b/pype/settings/defaults/launchers/darwin/python3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open /usr/bin/python3 --args $@ diff --git a/pype/settings/defaults/launchers/harmony_17.toml b/pype/settings/defaults/launchers/harmony_17.toml new file mode 100644 index 0000000000..dd1c929b1b --- /dev/null +++ b/pype/settings/defaults/launchers/harmony_17.toml @@ -0,0 +1,9 @@ +application_dir = "harmony" +label = "Harmony" +label_variant = "17" +ftrack_label = "Harmony" +schema = "avalon-core:application-1.0" +executable = "harmony_17" +description = "" +icon = "app_icons/harmony.png" +ftrack_icon = '{}/app_icons/harmony.png' diff --git a/pype/settings/defaults/launchers/houdini_16.toml b/pype/settings/defaults/launchers/houdini_16.toml new file mode 100644 index 0000000000..0a0876a264 --- /dev/null +++ b/pype/settings/defaults/launchers/houdini_16.toml @@ -0,0 +1,8 @@ +executable = "houdini_16" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini" +label_variant = "16" +ftrack_label = "Houdini" +icon = "app_icons/houdini.png" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/launchers/houdini_17.toml b/pype/settings/defaults/launchers/houdini_17.toml new file mode 100644 index 0000000000..203f5cdb9b --- /dev/null +++ b/pype/settings/defaults/launchers/houdini_17.toml @@ -0,0 +1,8 @@ +executable = "houdini_17" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini" +label_variant = "17" +ftrack_label = "Houdini" +icon = "app_icons/houdini.png" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/launchers/houdini_18.toml b/pype/settings/defaults/launchers/houdini_18.toml new file mode 100644 index 0000000000..40f530c291 --- /dev/null +++ b/pype/settings/defaults/launchers/houdini_18.toml @@ -0,0 +1,8 @@ +executable = "houdini_18" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini" +label_variant = "18" +ftrack_label = "Houdini" +icon = "app_icons/houdini.png" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/launchers/linux/maya2016 b/pype/settings/defaults/launchers/linux/maya2016 new file mode 100644 index 0000000000..98424304b1 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/maya2017 b/pype/settings/defaults/launchers/linux/maya2017 new file mode 100644 index 0000000000..7a2662a55e --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/maya2018 b/pype/settings/defaults/launchers/linux/maya2018 new file mode 100644 index 0000000000..db832b3fe7 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/maya2019 b/pype/settings/defaults/launchers/linux/maya2019 new file mode 100644 index 0000000000..8398734ab9 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/maya2020 b/pype/settings/defaults/launchers/linux/maya2020 new file mode 100644 index 0000000000..18a1edd598 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/nuke11.3 b/pype/settings/defaults/launchers/linux/nuke11.3 new file mode 100644 index 0000000000..b1c9a90d74 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/nuke12.0 b/pype/settings/defaults/launchers/linux/nuke12.0 new file mode 100644 index 0000000000..99ea1a6b0c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/nukestudio11.3 b/pype/settings/defaults/launchers/linux/nukestudio11.3 new file mode 100644 index 0000000000..750d54a7d5 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/nukestudio12.0 b/pype/settings/defaults/launchers/linux/nukestudio12.0 new file mode 100644 index 0000000000..ba5cf654a8 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/nukex11.3 b/pype/settings/defaults/launchers/linux/nukex11.3 new file mode 100644 index 0000000000..d913e4b961 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/linux/nukex12.0 b/pype/settings/defaults/launchers/linux/nukex12.0 new file mode 100644 index 0000000000..da2721c48b --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/maya_2016.toml b/pype/settings/defaults/launchers/maya_2016.toml new file mode 100644 index 0000000000..24a463d9c6 --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2016.toml @@ -0,0 +1,27 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya" +label_variant = "2016" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2016" +description = "" +icon = "app_icons/maya.png" +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/settings/defaults/launchers/maya_2017.toml b/pype/settings/defaults/launchers/maya_2017.toml new file mode 100644 index 0000000000..5295862e87 --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2017.toml @@ -0,0 +1,29 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya" +label_variant = "2017" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2017" +description = "" +icon = "app_icons/maya.png" +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/settings/defaults/launchers/maya_2018.toml b/pype/settings/defaults/launchers/maya_2018.toml new file mode 100644 index 0000000000..2bdff2094d --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2018.toml @@ -0,0 +1,15 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya" +label_variant = "2018" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2018" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/launchers/maya_2019.toml b/pype/settings/defaults/launchers/maya_2019.toml new file mode 100644 index 0000000000..8eb88179f9 --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2019.toml @@ -0,0 +1,15 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya" +label_variant = "2019" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2019" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/launchers/maya_2020.toml b/pype/settings/defaults/launchers/maya_2020.toml new file mode 100644 index 0000000000..693de0cf9e --- /dev/null +++ b/pype/settings/defaults/launchers/maya_2020.toml @@ -0,0 +1,15 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya" +label_variant = "2020" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2020" +description = "" +icon = "app_icons/maya.png" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/launchers/mayabatch_2019.toml b/pype/settings/defaults/launchers/mayabatch_2019.toml new file mode 100644 index 0000000000..a928618d2b --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/mayabatch_2020.toml b/pype/settings/defaults/launchers/mayabatch_2020.toml new file mode 100644 index 0000000000..cd1e1e4474 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/mayapy2016.toml b/pype/settings/defaults/launchers/mayapy2016.toml new file mode 100644 index 0000000000..ad1e3dee86 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/mayapy2017.toml b/pype/settings/defaults/launchers/mayapy2017.toml new file mode 100644 index 0000000000..8d2095ff47 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/mayapy2018.toml b/pype/settings/defaults/launchers/mayapy2018.toml new file mode 100644 index 0000000000..597744fd85 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/mayapy2019.toml b/pype/settings/defaults/launchers/mayapy2019.toml new file mode 100644 index 0000000000..3c8a9860f9 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/mayapy2020.toml b/pype/settings/defaults/launchers/mayapy2020.toml new file mode 100644 index 0000000000..8f2d2e4a67 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/myapp.toml b/pype/settings/defaults/launchers/myapp.toml new file mode 100644 index 0000000000..21da0d52b2 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/nuke_10.0.toml b/pype/settings/defaults/launchers/nuke_10.0.toml new file mode 100644 index 0000000000..d4dd028942 --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_10.0.toml @@ -0,0 +1,8 @@ +executable = "nuke10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "10.0v4" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_11.0.toml b/pype/settings/defaults/launchers/nuke_11.0.toml new file mode 100644 index 0000000000..10ff6aca37 --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_11.0.toml @@ -0,0 +1,8 @@ +executable = "nuke11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "11.0" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_11.2.toml b/pype/settings/defaults/launchers/nuke_11.2.toml new file mode 100644 index 0000000000..530c7f610e --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_11.2.toml @@ -0,0 +1,8 @@ +executable = "nuke11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "11.2" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_11.3.toml b/pype/settings/defaults/launchers/nuke_11.3.toml new file mode 100644 index 0000000000..c9ff005feb --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_11.3.toml @@ -0,0 +1,8 @@ +executable = "nuke11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "11.3" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nuke_12.0.toml b/pype/settings/defaults/launchers/nuke_12.0.toml new file mode 100644 index 0000000000..9ac1084fbf --- /dev/null +++ b/pype/settings/defaults/launchers/nuke_12.0.toml @@ -0,0 +1,8 @@ +executable = "nuke12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke" +label_variant = "12.0" +ftrack_label = "Nuke" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_10.0.toml b/pype/settings/defaults/launchers/nukestudio_10.0.toml new file mode 100644 index 0000000000..6c554aff62 --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_10.0.toml @@ -0,0 +1,8 @@ +executable = "nukestudio10.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "10.0" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_11.0.toml b/pype/settings/defaults/launchers/nukestudio_11.0.toml new file mode 100644 index 0000000000..482aa6587e --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_11.0.toml @@ -0,0 +1,8 @@ +executable = "nukestudio11.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "11.0" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_11.2.toml b/pype/settings/defaults/launchers/nukestudio_11.2.toml new file mode 100644 index 0000000000..78d1de3d8b --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_11.2.toml @@ -0,0 +1,8 @@ +executable = "nukestudio11.2" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "11.2" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_11.3.toml b/pype/settings/defaults/launchers/nukestudio_11.3.toml new file mode 100644 index 0000000000..35c6a08b2f --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_11.3.toml @@ -0,0 +1,8 @@ +executable = "nukestudio11.3" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "11.3" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukestudio_12.0.toml b/pype/settings/defaults/launchers/nukestudio_12.0.toml new file mode 100644 index 0000000000..2754116aef --- /dev/null +++ b/pype/settings/defaults/launchers/nukestudio_12.0.toml @@ -0,0 +1,8 @@ +executable = "nukestudio12.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio" +label_variant = "12.0" +ftrack_label = "NukeStudio" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/launchers/nukex_10.0.toml b/pype/settings/defaults/launchers/nukex_10.0.toml new file mode 100644 index 0000000000..48da30fe16 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_10.0.toml @@ -0,0 +1,8 @@ +executable = "nukex10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "10.0" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_11.0.toml b/pype/settings/defaults/launchers/nukex_11.0.toml new file mode 100644 index 0000000000..8f353e9e00 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_11.0.toml @@ -0,0 +1,8 @@ +executable = "nukex11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "11.0" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_11.2.toml b/pype/settings/defaults/launchers/nukex_11.2.toml new file mode 100644 index 0000000000..38e37fa4c9 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_11.2.toml @@ -0,0 +1,8 @@ +executable = "nukex11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "11.2" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_11.3.toml b/pype/settings/defaults/launchers/nukex_11.3.toml new file mode 100644 index 0000000000..42969c5e69 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_11.3.toml @@ -0,0 +1,8 @@ +executable = "nukex11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "11.3" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/nukex_12.0.toml b/pype/settings/defaults/launchers/nukex_12.0.toml new file mode 100644 index 0000000000..19d27a12d7 --- /dev/null +++ b/pype/settings/defaults/launchers/nukex_12.0.toml @@ -0,0 +1,8 @@ +executable = "nukex12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX" +label_variant = "12.0" +ftrack_label = "NukeX" +icon = "app_icons/nuke.png" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/launchers/photoshop_2020.toml b/pype/settings/defaults/launchers/photoshop_2020.toml new file mode 100644 index 0000000000..8164af929f --- /dev/null +++ b/pype/settings/defaults/launchers/photoshop_2020.toml @@ -0,0 +1,9 @@ +executable = "photoshop_2020" +schema = "avalon-core:application-1.0" +application_dir = "photoshop" +label = "Adobe Photoshop" +label_variant = "2020" +icon = "app_icons/photoshop.png" +ftrack_label = "Photoshop" +ftrack_icon = '{}/app_icons/photoshop.png' +launch_hook = "pype/hooks/photoshop/prelaunch.py/PhotoshopPrelaunch" diff --git a/pype/settings/defaults/launchers/premiere_2019.toml b/pype/settings/defaults/launchers/premiere_2019.toml new file mode 100644 index 0000000000..d03395e022 --- /dev/null +++ b/pype/settings/defaults/launchers/premiere_2019.toml @@ -0,0 +1,9 @@ +executable = "premiere_pro_2019" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC" +label_variant = "2019" +icon = "app_icons/premiere.png" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/settings/defaults/launchers/premiere_2020.toml b/pype/settings/defaults/launchers/premiere_2020.toml new file mode 100644 index 0000000000..01c7b5b745 --- /dev/null +++ b/pype/settings/defaults/launchers/premiere_2020.toml @@ -0,0 +1,10 @@ +executable = "premiere_pro_2020" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC" +label_variant = "2020" +launch_hook = "pype/hooks/premiere/prelaunch.py/PremierePrelaunch" +icon = "app_icons/premiere.png" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/settings/defaults/launchers/python_2.toml b/pype/settings/defaults/launchers/python_2.toml new file mode 100644 index 0000000000..f1c1ca7e68 --- /dev/null +++ b/pype/settings/defaults/launchers/python_2.toml @@ -0,0 +1,12 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python" +label = "Python" +label_variant = "2" +icon = "app_icons/python.png" + +ftrack_label = "Python" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/launchers/python_3.toml b/pype/settings/defaults/launchers/python_3.toml new file mode 100644 index 0000000000..90fb10eaeb --- /dev/null +++ b/pype/settings/defaults/launchers/python_3.toml @@ -0,0 +1,12 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python3" +label = "Python" +label_variant = "3" +icon = "app_icons/python.png" + +ftrack_label = "Python" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/launchers/resolve_16.toml b/pype/settings/defaults/launchers/resolve_16.toml new file mode 100644 index 0000000000..47918a22a6 --- /dev/null +++ b/pype/settings/defaults/launchers/resolve_16.toml @@ -0,0 +1,10 @@ +executable = "resolve_16" +schema = "avalon-core:application-1.0" +application_dir = "resolve" +label = "BM DaVinci Resolve" +label_variant = "16" +launch_hook = "pype/hooks/resolve/prelaunch.py/ResolvePrelaunch" +icon = "app_icons/resolve.png" + +ftrack_label = "BM DaVinci Resolve" +ftrack_icon = '{}/app_icons/resolve.png' diff --git a/pype/settings/defaults/launchers/shell.toml b/pype/settings/defaults/launchers/shell.toml new file mode 100644 index 0000000000..959ad392ea --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/storyboardpro_7.toml b/pype/settings/defaults/launchers/storyboardpro_7.toml new file mode 100644 index 0000000000..067f10a23a --- /dev/null +++ b/pype/settings/defaults/launchers/storyboardpro_7.toml @@ -0,0 +1,9 @@ +application_dir = "storyboardpro" +label = "Storyboard Pro" +label_variant = "7" +ftrack_label = "Storyboard Pro" +schema = "avalon-core:application-1.0" +executable = "storyboardpro_7" +description = "" +icon = "app_icons/storyboardpro.png" +ftrack_icon = '{}/app_icons/storyboardpro.png' diff --git a/pype/settings/defaults/launchers/unreal_4.24.toml b/pype/settings/defaults/launchers/unreal_4.24.toml new file mode 100644 index 0000000000..10b14e7f59 --- /dev/null +++ b/pype/settings/defaults/launchers/unreal_4.24.toml @@ -0,0 +1,10 @@ +executable = "unreal" +schema = "avalon-core:application-1.0" +application_dir = "unreal" +label = "Unreal Editor" +label_variant = "4.24" +icon = "app_icons/ue4.png" +launch_hook = "pype/hooks/unreal/unreal_prelaunch.py/UnrealPrelaunch" + +ftrack_label = "UnrealEditor" +ftrack_icon = '{}/app_icons/ue4.png' diff --git a/pype/settings/defaults/launchers/windows/blender_2.80.bat b/pype/settings/defaults/launchers/windows/blender_2.80.bat new file mode 100644 index 0000000000..5b8a37356b --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/blender_2.81.bat b/pype/settings/defaults/launchers/windows/blender_2.81.bat new file mode 100644 index 0000000000..a900b18eda --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/blender_2.82.bat b/pype/settings/defaults/launchers/windows/blender_2.82.bat new file mode 100644 index 0000000000..7105c1efe1 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/blender_2.83.bat b/pype/settings/defaults/launchers/windows/blender_2.83.bat new file mode 100644 index 0000000000..671952f0d7 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/celaction_local.bat b/pype/settings/defaults/launchers/windows/celaction_local.bat new file mode 100644 index 0000000000..8f2171617e --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/celaction_publish.bat b/pype/settings/defaults/launchers/windows/celaction_publish.bat new file mode 100644 index 0000000000..77ec2ac24e --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/harmony_17.bat b/pype/settings/defaults/launchers/windows/harmony_17.bat new file mode 100644 index 0000000000..0822650875 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/houdini_16.bat b/pype/settings/defaults/launchers/windows/houdini_16.bat new file mode 100644 index 0000000000..018ba08b4c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/houdini_17.bat b/pype/settings/defaults/launchers/windows/houdini_17.bat new file mode 100644 index 0000000000..950a599623 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/houdini_18.bat b/pype/settings/defaults/launchers/windows/houdini_18.bat new file mode 100644 index 0000000000..3d6b1ae258 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/maya2016.bat b/pype/settings/defaults/launchers/windows/maya2016.bat new file mode 100644 index 0000000000..54f15cf269 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/maya2017.bat b/pype/settings/defaults/launchers/windows/maya2017.bat new file mode 100644 index 0000000000..5c2aeb495c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/maya2018.bat b/pype/settings/defaults/launchers/windows/maya2018.bat new file mode 100644 index 0000000000..28cf776c77 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/maya2019.bat b/pype/settings/defaults/launchers/windows/maya2019.bat new file mode 100644 index 0000000000..7e80dd2557 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/maya2020.bat b/pype/settings/defaults/launchers/windows/maya2020.bat new file mode 100644 index 0000000000..b2acb5df5a --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayabatch2019.bat b/pype/settings/defaults/launchers/windows/mayabatch2019.bat new file mode 100644 index 0000000000..ddd9b9b956 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayabatch2020.bat b/pype/settings/defaults/launchers/windows/mayabatch2020.bat new file mode 100644 index 0000000000..b1cbc6dbb6 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayapy2016.bat b/pype/settings/defaults/launchers/windows/mayapy2016.bat new file mode 100644 index 0000000000..205991fd3d --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayapy2017.bat b/pype/settings/defaults/launchers/windows/mayapy2017.bat new file mode 100644 index 0000000000..14aacc5a7f --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayapy2018.bat b/pype/settings/defaults/launchers/windows/mayapy2018.bat new file mode 100644 index 0000000000..c47c472f46 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayapy2019.bat b/pype/settings/defaults/launchers/windows/mayapy2019.bat new file mode 100644 index 0000000000..73ca5b2d40 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/mayapy2020.bat b/pype/settings/defaults/launchers/windows/mayapy2020.bat new file mode 100644 index 0000000000..770a03dcf5 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nuke10.0.bat b/pype/settings/defaults/launchers/windows/nuke10.0.bat new file mode 100644 index 0000000000..a47cbdfb20 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nuke11.0.bat b/pype/settings/defaults/launchers/windows/nuke11.0.bat new file mode 100644 index 0000000000..a374c5cf5b --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nuke11.2.bat b/pype/settings/defaults/launchers/windows/nuke11.2.bat new file mode 100644 index 0000000000..4c777ac28c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nuke11.3.bat b/pype/settings/defaults/launchers/windows/nuke11.3.bat new file mode 100644 index 0000000000..a023f5f46f --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nuke12.0.bat b/pype/settings/defaults/launchers/windows/nuke12.0.bat new file mode 100644 index 0000000000..d8fb5772bb --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukestudio10.0.bat b/pype/settings/defaults/launchers/windows/nukestudio10.0.bat new file mode 100644 index 0000000000..82f833667c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukestudio11.0.bat b/pype/settings/defaults/launchers/windows/nukestudio11.0.bat new file mode 100644 index 0000000000..b66797727e --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukestudio11.2.bat b/pype/settings/defaults/launchers/windows/nukestudio11.2.bat new file mode 100644 index 0000000000..a653d816b4 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukestudio11.3.bat b/pype/settings/defaults/launchers/windows/nukestudio11.3.bat new file mode 100644 index 0000000000..62c8718873 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukestudio12.0.bat b/pype/settings/defaults/launchers/windows/nukestudio12.0.bat new file mode 100644 index 0000000000..488232bcbf --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukex10.0.bat b/pype/settings/defaults/launchers/windows/nukex10.0.bat new file mode 100644 index 0000000000..1759706a7b --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukex11.0.bat b/pype/settings/defaults/launchers/windows/nukex11.0.bat new file mode 100644 index 0000000000..b554a7b6fa --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukex11.2.bat b/pype/settings/defaults/launchers/windows/nukex11.2.bat new file mode 100644 index 0000000000..a4cb5dec5c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukex11.3.bat b/pype/settings/defaults/launchers/windows/nukex11.3.bat new file mode 100644 index 0000000000..490b55cf4c --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/nukex12.0.bat b/pype/settings/defaults/launchers/windows/nukex12.0.bat new file mode 100644 index 0000000000..26adf0d3f1 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/photoshop_2020.bat b/pype/settings/defaults/launchers/windows/photoshop_2020.bat new file mode 100644 index 0000000000..6b90922ef6 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/premiere_pro_2019.bat b/pype/settings/defaults/launchers/windows/premiere_pro_2019.bat new file mode 100644 index 0000000000..4886737d2f --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/premiere_pro_2020.bat b/pype/settings/defaults/launchers/windows/premiere_pro_2020.bat new file mode 100644 index 0000000000..14662d3be3 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/python3.bat b/pype/settings/defaults/launchers/windows/python3.bat new file mode 100644 index 0000000000..c7c116fe72 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/resolve_16.bat b/pype/settings/defaults/launchers/windows/resolve_16.bat new file mode 100644 index 0000000000..1a5d964e6b --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/shell.bat b/pype/settings/defaults/launchers/windows/shell.bat new file mode 100644 index 0000000000..eb0895364f --- /dev/null +++ b/pype/settings/defaults/launchers/windows/shell.bat @@ -0,0 +1,2 @@ +@echo off +start cmd diff --git a/pype/settings/defaults/launchers/windows/storyboardpro_7.bat b/pype/settings/defaults/launchers/windows/storyboardpro_7.bat new file mode 100644 index 0000000000..122edac572 --- /dev/null +++ b/pype/settings/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/settings/defaults/launchers/windows/unreal.bat b/pype/settings/defaults/launchers/windows/unreal.bat new file mode 100644 index 0000000000..7771aaa5a5 --- /dev/null +++ b/pype/settings/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/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json b/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json new file mode 100644 index 0000000000..02e6a9b95d --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/maya/maya_capture.json @@ -0,0 +1,108 @@ +{ + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.714, + 0.714, + 0.714 + ], + "backgroundBottom": [ + 0.714, + 0.714, + 0.714 + ], + "backgroundTop": [ + 0.714, + 0.714, + 0.714 + ], + "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": { + "width": 1920, + "height": 1080, + "percent": 1.0, + "mode": "Custom" + }, + "Time Range": { + "start_frame": 0, + "end_frame": 25, + "frame": "", + "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 + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json b/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json new file mode 100644 index 0000000000..486f0917e2 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/maya/publish.json @@ -0,0 +1,21 @@ +{ + "ValidateModelName": { + "enabled": true, + "material_file": { + "windows": "", + "darwin": "", + "linux": "" + }, + "regex": "" + }, + "ValidateAssemblyName": { + "enabled": true + }, + "ValidateShaderName": { + "enabled": true, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": true + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json b/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/maya/workfile_build.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/environments/photoshop.json b/pype/settings/defaults/system_settings/environments/photoshop.json deleted file mode 100644 index 2208a88665..0000000000 --- a/pype/settings/defaults/system_settings/environments/photoshop.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", - "PYTHONPATH": "{PYTHONPATH}" -} diff --git a/pype/settings/defaults/system_settings/global/applications.json b/pype/settings/defaults/system_settings/global/applications.json index e85e5864d9..b85ec5369c 100644 --- a/pype/settings/defaults/system_settings/global/applications.json +++ b/pype/settings/defaults/system_settings/global/applications.json @@ -1,34 +1,862 @@ { - "blender_2.80": false, - "blender_2.81": false, - "blender_2.82": false, - "blender_2.83": true, - "celaction_local": true, - "celaction_remote": true, - "harmony_17": true, - "maya_2017": false, - "maya_2018": false, - "maya_2019": true, - "maya_2020": true, - "nuke_10.0": false, - "nuke_11.2": false, - "nuke_11.3": true, - "nuke_12.0": true, - "nukex_10.0": false, - "nukex_11.2": false, - "nukex_11.3": true, - "nukex_12.0": true, - "nukestudio_10.0": false, - "nukestudio_11.2": false, - "nukestudio_11.3": true, - "nukestudio_12.0": true, - "houdini_16": false, - "houdini_16.5": false, - "houdini_17": false, - "houdini_18": true, - "premiere_2019": false, - "premiere_2020": true, - "resolve_16": true, - "storyboardpro_7": true, - "unreal_4.24": true + "maya": { + "enabled": true, + "environment": { + "__environment_keys__": { + "maya": [ + "PYTHONPATH", + "MAYA_DISABLE_CLIC_IPM", + "MAYA_DISABLE_CIP", + "MAYA_DISABLE_CER", + "PYMEL_SKIP_MEL_INIT", + "LC_ALL", + "PYPE_LOG_NO_COLORS" + ] + }, + "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" + }, + "maya_2020": { + "enabled": true, + "maya_executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\maya2020\\bin\\maya.exe" + ], + "darwin": [ + "" + ], + "linux": [ + "/usr/autodesk/maya2020/bin/maya" + ] + }, + "environment": { + "__environment_keys__": { + "maya_2020": [ + "MAYA_VERSION", + "MAYA_LOCATION", + "DYLD_LIBRARY_PATH" + ] + }, + "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" + } + } + }, + "maya_2019": { + "enabled": true, + "maya_executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\maya2019\\bin\\maya.exe" + ], + "darwin": [ + "" + ], + "linux": [ + "/usr/autodesk/maya2019/bin/maya" + ] + }, + "environment": { + "__environment_keys__": { + "maya_2019": [ + "MAYA_VERSION", + "MAYA_LOCATION", + "DYLD_LIBRARY_PATH" + ] + }, + "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" + } + } + }, + "maya_2018": { + "enabled": true, + "maya_executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\maya2018\\bin\\maya.exe" + ], + "darwin": [ + "" + ], + "linux": [ + "/usr/autodesk/maya2018/bin/maya" + ] + }, + "environment": { + "__environment_keys__": { + "maya_2018": [ + "MAYA_VERSION", + "MAYA_LOCATION", + "DYLD_LIBRARY_PATH" + ] + }, + "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" + } + } + } + }, + "nuke": { + "enabled": true, + "environment": { + "__environment_keys__": { + "nuke": [ + "NUKE_PATH", + "PATH" + ] + }, + "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}" + } + }, + "nuke_12.0": { + "enabled": true, + "nuke_executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "environment": { + "__environment_keys__": { + "nuke_12.0": [] + } + } + }, + "nuke_11.3": { + "enabled": true, + "nuke_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v4\\Nuke11.3.exe", + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v4/Nuke11.3", + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ] + }, + "environment": { + "__environment_keys__": { + "nuke_11.3": [] + } + } + }, + "nuke_11.2": { + "enabled": true, + "nuke_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v3\\Nuke11.2.exe", + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe", + "C:\\Program Files\\Nuke11.2v1\\Nuke11.2.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.2v3/Nuke11.2", + "/usr/local/Nuke11.2v2/Nuke11.2", + "/usr/local/Nuke11.2v1/Nuke11.2" + ] + }, + "environment": { + "__environment_keys__": { + "nuke_11.2": [] + } + } + } + }, + "nukex": { + "enabled": true, + "environment": { + "__environment_keys__": { + "nukex": [ + "NUKE_PATH", + "PATH" + ] + }, + "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}" + } + }, + "nukex_12.0": { + "enabled": true, + "nukex_executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe -nukex" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0 -nukex" + ] + }, + "environment": { + "__environment_keys__": { + "nukex_12.0": [] + } + } + }, + "nukex_11.3": { + "enabled": true, + "nukex_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe -nukex" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v1/Nuke11.3 -nukex" + ] + }, + "environment": { + "__environment_keys__": { + "nukex_11.3": [] + } + } + }, + "nukex_11.2": { + "enabled": true, + "nukex_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v3\\Nuke11.2.exe -nukex" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.2v3/Nuke11.2 -nukex" + ] + }, + "environment": { + "__environment_keys__": { + "nukex_11.2": [] + } + } + } + }, + "nukestudio": { + "enabled": true, + "environment": { + "__environment_keys__": { + "nukestudio": [ + "HIERO_PLUGIN_PATH", + "PATH", + "WORKFILES_STARTUP", + "TAG_ASSETBUILD_STARTUP", + "PYPE_LOG_NO_COLORS" + ] + }, + "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" + }, + "nukestudio_12.0": { + "enabled": true, + "nukestudio_executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe -studio" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0 -studio" + ] + }, + "environment": { + "__environment_keys__": { + "nukestudio_12.0": [] + } + } + }, + "nukestudio_11.3": { + "enabled": true, + "nukestudio_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe -studio" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v1/Nuke11.3 -studio" + ] + }, + "environment": { + "__environment_keys__": { + "nukestudio_11.3": [] + } + } + }, + "nukestudio_11.2": { + "enabled": true, + "nukestudio_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v3\\Nuke11.2.exe -studio" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.2v3/Nuke11.2 -studio" + ] + }, + "environment": { + "__environment_keys__": { + "nukestudio_11.2": [] + } + } + } + }, + "hiero": { + "enabled": true, + "environment": { + "__environment_keys__": { + "hiero": [ + "HIERO_PLUGIN_PATH", + "PATH", + "WORKFILES_STARTUP", + "TAG_ASSETBUILD_STARTUP", + "PYPE_LOG_NO_COLORS" + ] + }, + "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" + }, + "hiero_12.0": { + "enabled": true, + "hiero_executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe -hiero" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0 -hiero" + ] + }, + "environment": { + "__environment_keys__": { + "hiero_12.0": [] + } + } + }, + "hiero_11.3": { + "enabled": true, + "hiero_executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe -hiero" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v1/Nuke11.3 -hiero" + ] + }, + "environment": { + "__environment_keys__": { + "hiero_11.3": [] + } + } + }, + "hiero_11.2": { + "enabled": true, + "hiero_executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "hiero_11.2": [] + } + } + } + }, + "fusion": { + "enabled": true, + "environment": { + "__environment_keys__": { + "fusion": [] + } + }, + "fusion_16": { + "enabled": true, + "fusion_executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "fusion_16": [] + } + } + }, + "fusion_9": { + "enabled": true, + "fusion_executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "fusion_9": [] + } + } + } + }, + "resolve": { + "enabled": true, + "environment": { + "__environment_keys__": { + "resolve": [ + "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR", + "RESOLVE_SCRIPT_API", + "RESOLVE_SCRIPT_LIB", + "RESOLVE_UTILITY_SCRIPTS_DIR", + "PYTHON36_RESOLVE", + "PYTHONPATH", + "PATH", + "PRE_PYTHON_SCRIPT", + "PYPE_LOG_NO_COLORS", + "RESOLVE_DEV" + ] + }, + "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" + }, + "resolve_16": { + "enabled": true, + "resolve_executables": { + "windows": [ + "C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "resolve_16": [] + } + } + } + }, + "houdini": { + "enabled": true, + "environment": { + "__environment_keys__": { + "houdini": [ + "HOUDINI_PATH", + "HOUDINI_MENU_PATH" + ] + }, + "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;&" + } + }, + "houdini_18": { + "enabled": true, + "houdini_executables": { + "windows": [ + "C:\\Program Files\\Side Effects Software\\Houdini 18.0.287\\bin\\houdini.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "houdini_18": [] + } + } + }, + "houdini_17": { + "enabled": true, + "houdini_executables": { + "windows": [ + "C:\\Program Files\\Side Effects Software\\Houdini 17.0.459\\bin\\houdini.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "houdini_17": [] + } + } + } + }, + "blender": { + "enabled": true, + "environment": { + "__environment_keys__": { + "blender": [ + "BLENDER_USER_SCRIPTS", + "PYTHONPATH", + "CREATE_NEW_CONSOLE" + ] + }, + "BLENDER_USER_SCRIPTS": "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", + "{PYTHONPATH}" + ], + "CREATE_NEW_CONSOLE": "yes" + }, + "blender_2.90": { + "enabled": true, + "blender_executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "blender_2.90": [] + } + } + }, + "blender_2.83": { + "enabled": true, + "blender_executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "blender_2.83": [] + } + } + } + }, + "harmony": { + "enabled": true, + "environment": { + "__environment_keys__": { + "harmony": [ + "AVALON_HARMONY_WORKFILES_ON_LAUNCH", + "PYBLISH_GUI_ALWAYS_EXEC" + ] + }, + "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1", + "PYBLISH_GUI_ALWAYS_EXEC": "1" + }, + "harmony_20": { + "enabled": true, + "harmony_executables": { + "windows": [ + "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 20 Premium/win64/bin/HarmonyPremium.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "harmony_20": [] + } + } + }, + "harmony_19": { + "enabled": true, + "harmony_executables": { + "windows": [ + "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 19 Premium/win64/bin/HarmonyPremium.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "harmony_19": [] + } + } + }, + "harmony_18": { + "enabled": true, + "harmony_executables": { + "windows": [ + "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "harmony_18": [] + } + } + }, + "harmony_17": { + "enabled": true, + "harmony_executables": { + "windows": [ + "C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "harmony_17": [] + } + } + } + }, + "photoshop": { + "enabled": true, + "environment": { + "__environment_keys__": { + "photoshop": [ + "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", + "PYTHONPATH", + "PYPE_LOG_NO_COLORS", + "WEBSOCKET_URL", + "WORKFILES_SAVE_AS" + ] + }, + "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", + "PYTHONPATH": "{PYTHONPATH}", + "PYPE_LOG_NO_COLORS": "Yes", + "WEBSOCKET_URL": "ws://localhost:8099/ws/", + "WORKFILES_SAVE_AS": "Yes" + }, + "photoshop_2020": { + "enabled": true, + "photoshop_executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "photoshop_2020": [] + } + } + } + }, + "celaction": { + "enabled": true, + "environment": { + "__environment_keys__": { + "celaction": [ + "CELACTION_TEMPLATE" + ] + }, + "CELACTION_TEMPLATE": "{PYPE_MODULE_ROOT}/pype/hosts/celaction/celaction_template_scene.scn" + }, + "celation_Local": { + "enabled": true, + "celation_executables": "C:\\Program Files (x86)\\CelAction\\CelAction2D.exe", + "environment": { + "__environment_keys__": { + "celation_Local": [] + } + } + }, + "celation_Publish": { + "enabled": true, + "celation_executables": "%PYPE_PYTHON_EXE% \"%PYPE_MODULE_ROOT%\\pype\\hosts\\celaction\\cli.py\" %*", + "environment": { + "__environment_keys__": { + "celation_Publish": [] + } + } + } + }, + "unreal": { + "enabled": true, + "environment": { + "__environment_keys__": { + "unreal": [] + } + }, + "unreal_4.24": { + "enabled": true, + "unreal_executables": { + "windows": [ + "%AVALON_CURRENT_UNREAL_ENGINE%\\Engine\\Binaries\\Win64\\UE4Editor.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "unreal_4.24": [] + } + } + } + }, + "shell": { + "enabled": true, + "environment": { + "__environment_keys__": { + "shell": [] + } + }, + "python_Python 3.7": { + "enabled": true, + "python_executables": { + "windows": [ + "C:\\Python37\\python.exe", + "C:\\Python36\\python.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "python_Python 3.7": [] + } + } + }, + "python_Python 2.7": { + "enabled": true, + "python_executables": { + "windows": [ + "C:\\Python27\\python.exe" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "python_Python 2.7": [] + } + } + }, + "terminal_Terminal": { + "enabled": true, + "terminal_executables": { + "windows": [ + "start cmd" + ], + "darwin": [], + "linux": [] + }, + "environment": { + "__environment_keys__": { + "terminal_Terminal": [] + } + } + } + }, + "djvview": { + "enabled": true, + "environment": { + "__environment_keys__": { + "djvview": [] + } + }, + "djvview_1.1": { + "enabled": true, + "djvview_executables": { + "windows": [ + "C:/Program Files/djv-1.1.0-Windows-64/bin/djv_view.exe", + "C:/Program Files/DJV/bin/djv_view.exe" + ], + "darwin": [ + "Application/DJV.app/Contents/MacOS/DJV" + ], + "linux": [ + "usr/local/djv/djv_view" + ] + }, + "environment": { + "__environment_keys__": { + "djvview_1.1": [] + } + } + } + } } \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/general.json b/pype/settings/defaults/system_settings/global/general.json index bd501b06eb..23e8a3ff5d 100644 --- a/pype/settings/defaults/system_settings/global/general.json +++ b/pype/settings/defaults/system_settings/global/general.json @@ -1,4 +1,53 @@ { - "studio_name": "", - "studio_code": "" + "studio_name": "convert from \"PYPE_STUDIO_NAME\"", + "studio_code": "convert from \"PYPE_STUDIO_CODE\"", + "project_plugins": { + "windows": "convert from \"PYPE_PROJECT_PLUGINS\"", + "darwin": "", + "linux": "" + }, + "studio_soft": { + "windows": "convert from \"STUDIO_SOFT\"", + "darwin": "", + "linux": "" + }, + "environment": { + "__environment_keys__": { + "global": [ + "PYPE_APP_ROOT", + "PYPE_MODULE_ROOT", + "FFMPEG_PATH", + "PATH", + "PYTHONPATH", + "PYPE_PROJECT_CONFIGS", + "PYPE_PYTHON_EXE", + "PYBLISH_GUI" + ] + }, + "PYPE_APP_ROOT": "{PYPE_SETUP_PATH}/pypeapp", + "PYPE_MODULE_ROOT": "{PYPE_SETUP_PATH}/repos/pype", + "FFMPEG_PATH": { + "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/windows/bin", + "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/darwin/bin", + "linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/bin/ffmpeg_exec/linux" + }, + "PATH": [ + "{PYPE_CONFIG}/launchers", + "{PYPE_APP_ROOT}", + "{FFMPEG_PATH}", + "{PATH}" + ], + "PYTHONPATH": { + "windows": "{VIRTUAL_ENV}/Lib/site-packages;{PYPE_MODULE_ROOT}/pype/tools;{PYPE_MODULE_ROOT}/pype/vendor;{PYTHONPATH}", + "linux": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYPE_MODULE_ROOT}/pype/vendor:{PYTHONPATH}", + "darwin": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYPE_MODULE_ROOT}/pype/vendor:{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" + } } \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/hosts.json b/pype/settings/defaults/system_settings/global/hosts.json new file mode 100644 index 0000000000..35ee708df3 --- /dev/null +++ b/pype/settings/defaults/system_settings/global/hosts.json @@ -0,0 +1,34 @@ +{ + "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, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nuke_10.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": 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": true, + "houdini_17": true, + "houdini_18": true, + "premiere_2019": true, + "premiere_2020": true, + "resolve_16": true, + "storyboardpro_7": true, + "unreal_4.24": true +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/modules.json b/pype/settings/defaults/system_settings/global/modules.json index 9bd46602cf..b0245f52bd 100644 --- a/pype/settings/defaults/system_settings/global/modules.json +++ b/pype/settings/defaults/system_settings/global/modules.json @@ -2,7 +2,23 @@ "Avalon": { "AVALON_MONGO": "mongodb://localhost:2707", "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", - "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails" + "AVALON_THUMBNAIL_ROOT": "{PYPE_SETUP_PATH}/../avalon_thumails", + "environment": { + "__environment_keys__": { + "avalon": [ + "AVALON_CONFIG", + "AVALON_PROJECTS", + "AVALON_SCHEMA", + "AVALON_LABEL", + "AVALON_TIMEOUT" + ] + }, + "AVALON_CONFIG": "pype", + "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}", + "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema", + "AVALON_LABEL": "Pype", + "AVALON_TIMEOUT": "1000" + } }, "Ftrack": { "enabled": true, @@ -34,6 +50,24 @@ "test": "Test" }, "default": "-" + }, + "environment": { + "__environment_keys__": { + "ftrack": [ + "FTRACK_ACTIONS_PATH", + "FTRACK_EVENTS_PATH", + "PYBLISHPLUGINPATH" + ] + }, + "FTRACK_ACTIONS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions" + ], + "FTRACK_EVENTS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events" + ], + "PYBLISHPLUGINPATH": [ + "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" + ] } }, "Rest Api": { diff --git a/pype/settings/lib.py b/pype/settings/lib.py index 388557ca9b..96c3829388 100644 --- a/pype/settings/lib.py +++ b/pype/settings/lib.py @@ -6,9 +6,11 @@ import copy log = logging.getLogger(__name__) # Metadata keys for work with studio and project overrides -OVERRIDEN_KEY = "__overriden_keys__" +M_OVERRIDEN_KEY = "__overriden_keys__" +# Metadata key for storing information about environments +M_ENVIRONMENT_KEY = "__environment_keys__" # NOTE key popping not implemented yet -POP_KEY = "__pop_key__" +M_POP_KEY = "__pop_key__" # Folder where studio overrides are stored STUDIO_OVERRIDES_PATH = os.environ["PYPE_PROJECT_CONFIGS"] @@ -19,6 +21,12 @@ SYSTEM_SETTINGS_PATH = os.path.join( STUDIO_OVERRIDES_PATH, SYSTEM_SETTINGS_KEY + ".json" ) +# File where studio's environment overrides are stored +ENVIRONMENTS_KEY = "environments" +ENVIRONMENTS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, ENVIRONMENTS_KEY + ".json" +) + # File where studio's default project overrides are stored PROJECT_SETTINGS_KEY = "project_settings" PROJECT_SETTINGS_FILENAME = PROJECT_SETTINGS_KEY + ".json" @@ -105,6 +113,32 @@ def load_json(fpath): return {} +def find_environments(data): + if not data or not isinstance(data, dict): + return + + output = {} + if M_ENVIRONMENT_KEY in data: + metadata = data.pop(M_ENVIRONMENT_KEY) + for env_group_key, env_keys in metadata.items(): + output[env_group_key] = {} + for key in env_keys: + output[env_group_key][key] = data[key] + + for value in data.values(): + result = find_environments(value) + if not result: + continue + + for env_group_key, env_values in result.items(): + if env_group_key not in output: + output[env_group_key] = {} + + for env_key, env_value in env_values.items(): + output[env_group_key][env_key] = env_value + return output + + def subkey_merge(_dict, value, keys): key = keys.pop(0) if not keys: @@ -162,6 +196,12 @@ def studio_system_settings(): return {} +def studio_environments(): + if os.path.exists(ENVIRONMENTS_PATH): + return load_json(ENVIRONMENTS_PATH) + return {} + + def studio_project_settings(): if os.path.exists(PROJECT_SETTINGS_PATH): return load_json(PROJECT_SETTINGS_PATH) @@ -211,13 +251,13 @@ def project_anatomy_overrides(project_name): def merge_overrides(global_dict, override_dict): - if OVERRIDEN_KEY in override_dict: - overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) + if M_OVERRIDEN_KEY in override_dict: + overriden_keys = set(override_dict.pop(M_OVERRIDEN_KEY)) else: overriden_keys = set() for key, value in override_dict.items(): - if value == POP_KEY: + if value == M_POP_KEY: global_dict.pop(key) elif ( @@ -256,3 +296,11 @@ def project_settings(project_name): project_overrides = project_settings_overrides(project_name) return apply_overrides(studio_overrides, project_overrides) + + +def environments(): + envs = copy.deepcopy(default_settings()[ENVIRONMENTS_KEY]) + envs_from_system_settings = find_environments(system_settings()) + for env_group_key, values in envs_from_system_settings.items(): + envs[env_group_key] = values + return envs diff --git a/pype/tools/pyblish_pype/widgets.py b/pype/tools/pyblish_pype/widgets.py index 4da759899e..dc4919c13f 100644 --- a/pype/tools/pyblish_pype/widgets.py +++ b/pype/tools/pyblish_pype/widgets.py @@ -543,7 +543,9 @@ class TerminalFilterWidget(QtWidgets.QWidget): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) # Add spacers - layout.addWidget(QtWidgets.QWidget(), 1) + spacer = QtWidgets.QWidget() + spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(spacer, 1) for btn in filter_buttons: layout.addWidget(btn) diff --git a/pype/tools/settings/settings/README.md b/pype/tools/settings/settings/README.md index 47bbf28ba5..4f4e9d305a 100644 --- a/pype/tools/settings/settings/README.md +++ b/pype/tools/settings/settings/README.md @@ -19,6 +19,7 @@ - GUI schemas are huge json files, to be able to split whole configuration into multiple schema there's type `schema` - system configuration schemas are stored in `~/tools/settings/settings/gui_schemas/system_schema/` and project configurations in `~/tools/settings/settings/gui_schemas/projects_schema/` - each schema name is filename of json file except extension (without ".json") +- if content is dictionary content will be used as `schema` else will be used as `schema_template` ### schema - can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represebts name of the schema @@ -31,6 +32,87 @@ } ``` +### schema_template +- allows to define schema "templates" to not duplicate same content multiple times +```javascript +// EXAMPLE json file content (filename: example_template.json) +[ + { + "__default_values__": { + "multipath_executables": true + } + }, { + "type": "raw-json", + "label": "{host_label} Environments", + "key": "{host_name}_environments", + "env_group_key": "{host_name}" + }, { + "type": "path-widget", + "key": "{host_name}_executables", + "label": "{host_label} - Full paths to executables", + "multiplatform": "{multipath_executables}", + "multipath": true + } +] +``` +```javascript +// EXAMPLE usage of the template in schema +{ + "type": "dict", + "key": "schema_template_examples", + "label": "Schema template examples", + "children": [ + { + "type": "schema_template", + // filename of template (example_template.json) + "name": "example_template", + "template_data": { + "host_label": "Maya 2019", + "host_name": "maya_2019", + "multipath_executables": false + } + }, { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Maya 2020", + "host_name": "maya_2020" + } + } + ] +} +``` +- item in schema mush contain `"type"` and `"name"` keys but it is also expected that `"template_data"` will be entered too +- all items in the list, except `__default_values__`, will replace `schema_template` item in schema +- template may contain another template or schema +- it is expected that schema template will have unfilled fields as in example + - unfilled fields are allowed only in values of schema dictionary +```javascript +{ + ... + // Allowed + "key": "{to_fill}" + ... + // Not allowed + "{to_fill}": "value" + ... +} +``` +- Unfilled fields can be also used for non string values, in that case value must contain only one key and value for fill must contain right type. +```javascript +{ + ... + // Allowed + "multiplatform": "{executable_multiplatform}" + ... + // Not allowed + "multiplatform": "{executable_multiplatform}_enhanced_string" + ... +} +``` +- It is possible to define default values for unfilled fields to do so one of items in list must be dictionary with key `"__default_values__"` and value as dictionary with default key: values (as in example above). + + ## Basic Dictionary inputs - these inputs wraps another inputs into {key: value} relation @@ -84,8 +166,8 @@ "type": "list", "key": "profiles", "label": "Profiles", - "object_type": "dict-item", - "input_modifiers": { + "object_type": { + "type": "dict-item", "children": [ { "key": "families", @@ -168,6 +250,29 @@ } ``` +### enum +- returns value of single on multiple items from predefined values +- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`) +- values are defined under value of key `"enum_items"` as list + - each item in list is simple dictionary where value is label and key is value which will be stored + - should be possible to enter single dictionary if order of items doesn't matter + +``` +{ + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [ + {"burnin": "Add burnins"}, + {"ftrackreview": "Add to Ftrack"}, + {"delete": "Delete output"}, + {"slate-frame": "Add slate frame"}, + {"no-hnadles": "Skip handle frames"} + ] +} +``` + ## Inputs for setting value using Pure inputs - these inputs also have required `"key"` and `"label"` - they use Pure inputs "as widgets" @@ -176,37 +281,53 @@ - 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 of items is defined with key `"object_type"` +- there are 2 possible ways how to set the type: + 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) + 2.) item type name as string without modifiers (e.g. `text`) +1.) with item modifiers ``` { "type": "list", - "object_type": "number", "key": "exclude_ports", "label": "Exclude ports", - "input_modifiers": { - "minimum": 1, - "maximum": 65535 + "object_type": { + "type": "number", # number item type + "minimum": 1, # minimum modifier + "maximum": 65535 # maximum modifier } } ``` +2.) without modifiers +``` +{ + "type": "list", + "key": "exclude_ports", + "label": "Exclude ports", + "object_type": "text" +} +``` + ### 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"` +- type of items is defined with key `"object_type"` +- there are 2 possible ways how to set the type: + 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) + 2.) item type name as string without modifiers (e.g. `text`) - 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`) +1.) with item modifiers ``` { "type": "dict-modifiable", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "minimum": 0, "maximum": 300 }, @@ -217,6 +338,18 @@ } ``` +2.) without modifiers +``` +{ + "type": "dict-modifiable", + "object_type": "text", + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true +} +``` + ### path-widget - input for paths, use `path-input` internally - has 2 input modifiers `"multiplatform"` and `"multipath"` 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 f357b51dc5..87912cfdc0 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 @@ -3,743 +3,612 @@ "collapsable": true, "key": "plugins", "label": "Plugins", - "children": [ - { + "children": [{ "type": "dict", "collapsable": true, "key": "celaction", "label": "CelAction", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractCelactionDeadline", - "label": "ExtractCelactionDeadline", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "deadline_department", - "label": "Deadline apartment" - }, { - "type": "number", - "key": "deadline_priority", - "label": "Deadline priority" - }, { - "type": "text", - "key": "deadline_pool", - "label": "Deadline pool" - }, { - "type": "text", - "key": "deadline_pool_secondary", - "label": "Deadline pool (secondary)" - }, { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - }, { - "type": "number", - "key": "deadline_chunk_size", - "label": "Deadline Chunk size" - } - ] - } - ] - } - ] + "checkbox_key": "enabled", + "key": "ExtractCelactionDeadline", + "label": "ExtractCelactionDeadline", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "deadline_department", + "label": "Deadline apartment" + }, { + "type": "number", + "key": "deadline_priority", + "label": "Deadline priority" + }, { + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" + }, { + "type": "text", + "key": "deadline_pool_secondary", + "label": "Deadline pool (secondary)" + }, { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk size" + }] + }] + }] }, { "type": "dict", "collapsable": true, "key": "ftrack", "label": "Ftrack", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "IntegrateFtrackNote", - "label": "IntegrateFtrackNote", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "note_with_intent_template", - "label": "Note with intent template" - }, { - "type": "list", - "object_type": "text", - "key": "note_labels", - "label": "Note labels" - } - ] - } - ] - } - ] + "checkbox_key": "enabled", + "key": "IntegrateFtrackNote", + "label": "IntegrateFtrackNote", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "note_with_intent_template", + "label": "Note with intent template" + }, { + "type": "list", + "object_type": "text", + "key": "note_labels", + "label": "Note labels" + }] + }] + }] }, { "type": "dict", "collapsable": true, "key": "global", "label": "Global", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "IntegrateMasterVersion", - "label": "IntegrateMasterVersion", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] + "checkbox_key": "enabled", + "key": "IntegrateMasterVersion", + "label": "IntegrateMasterVersion", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractJpegEXR", + "label": "ExtractJpegEXR", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-invisible", + "key": "ffmpeg_args", + "children": [{ + "type": "list", + "object_type": "text", + "key": "input", + "label": "FFmpeg input arguments" }, { + "type": "list", + "object_type": "text", + "key": "output", + "label": "FFmpeg output arguments" + }] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractReview", + "label": "ExtractReview", + "checkbox_key": "enabled", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "dict-invisible", - "key": "ffmpeg_args", - "children": [ - { - "type": "list", - "object_type": "text", - "key": "input", - "label": "FFmpeg input arguments" - }, { - "type": "list", - "object_type": "text", - "key": "output", - "label": "FFmpeg output arguments" - } - ] - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractReview", - "label": "ExtractReview", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "list", - "key": "profiles", - "label": "Profiles", - "object_type": "dict", - "input_modifiers": { - "children": [ + "children": [{ + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [{ + "key": "ext", + "label": "Output extension", + "type": "text" + }, { + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [{ + "burnin": "Add burnins" + }, { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - }, { - "key": "hosts", - "label": "Hosts", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "outputs", - "label": "Output Definitions", - "type": "dict-modifiable", - "highlight_content": true, - "object_type": "dict", - "input_modifiers": { - "children": [ - { - "key": "ext", - "label": "Output extension", - "type": "text" - }, { - "key": "tags", - "label": "Tags", - "type": "list", - "object_type": "text" - }, { - "key": "ffmpeg_args", - "label": "FFmpeg arguments", - "type": "dict", - "highlight_content": true, - "children": [ - { - "key": "video_filters", - "label": "Video filters", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "audio_filters", - "label": "Audio filters", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "input", - "label": "Input arguments", - "type": "list", - "object_type": "text" - }, { - "type": "splitter" - }, { - "key": "output", - "label": "Output arguments", - "type": "list", - "object_type": "text" - } - ] - }, { - "key": "filter", - "label": "Additional output filtering", - "type": "dict", - "highlight_content": true, - "children": [ - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - } - ] - } - ] - } + "ftrackreview": "Add to Ftrack" + }, + { + "delete": "Delete output" + }, + { + "slate-frame": "Add slate frame" + }, + { + "no-hnadles": "Skip handle frames" } ] - } + }, { + "key": "ffmpeg_args", + "label": "FFmpeg arguments", + "type": "dict", + "highlight_content": true, + "children": [{ + "key": "video_filters", + "label": "Video filters", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "audio_filters", + "label": "Audio filters", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "input", + "label": "Input arguments", + "type": "list", + "object_type": "text" + }, { + "type": "splitter" + }, { + "key": "output", + "label": "Output arguments", + "type": "list", + "object_type": "text" + }] + }, { + "key": "filter", + "label": "Additional output filtering", + "type": "dict", + "highlight_content": true, + "children": [{ + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }] + }] } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractBurnin", - "label": "ExtractBurnin", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "dict", - "collapsable": true, - "key": "options", - "label": "Burnin formating options", - "children": [ - { - "type": "number", - "key": "font_size", - "label": "Font size" - }, { - "type": "number", - "key": "opacity", - "label": "Font opacity" - }, { - "type": "number", - "key": "bg_opacity", - "label": "Background opacity" - }, { - "type": "number", - "key": "x_offset", - "label": "X Offset" - }, { - "type": "number", - "key": "y_offset", - "label": "Y Offset" - }, { - "type": "number", - "key": "bg_padding", - "label": "Padding aroung text" - } - ] - }, { - "type": "raw-json", - "key": "profiles", - "label": "Burnin profiles" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "IntegrateAssetNew", - "label": "IntegrateAssetNew", - "is_group": true, - "children": [ - { - "type": "raw-json", - "key": "template_name_profiles", - "label": "template_name_profiles" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ProcessSubmittedJobOnFarm", - "label": "ProcessSubmittedJobOnFarm", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "deadline_department", - "label": "Deadline department" - }, { - "type": "text", - "key": "deadline_pool", - "label": "Deadline Pool" - }, { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - } - ] + }] } - ] - } - ] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractBurnin", + "label": "ExtractBurnin", + "checkbox_key": "enabled", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict", + "collapsable": true, + "key": "options", + "label": "Burnin formating options", + "children": [{ + "type": "number", + "key": "font_size", + "label": "Font size" + }, { + "type": "number", + "key": "opacity", + "label": "Font opacity" + }, { + "type": "number", + "key": "bg_opacity", + "label": "Background opacity" + }, { + "type": "number", + "key": "x_offset", + "label": "X Offset" + }, { + "type": "number", + "key": "y_offset", + "label": "Y Offset" + }, { + "type": "number", + "key": "bg_padding", + "label": "Padding aroung text" + }] + }, { + "type": "raw-json", + "key": "profiles", + "label": "Burnin profiles" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "IntegrateAssetNew", + "label": "IntegrateAssetNew", + "is_group": true, + "children": [{ + "type": "raw-json", + "key": "template_name_profiles", + "label": "template_name_profiles" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ProcessSubmittedJobOnFarm", + "label": "ProcessSubmittedJobOnFarm", + "checkbox_key": "enabled", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "deadline_department", + "label": "Deadline department" + }, { + "type": "text", + "key": "deadline_pool", + "label": "Deadline Pool" + }, { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }] + }] + }] }, { - "type": "dict", + "type": "dict-invisible", "collapsable": true, "key": "maya", "label": "Maya", - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "ValidateModelName", - "label": "Validate Model Name", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "material_file", - "label": "Material File" - }, { - "type": "text", - "key": "regex", - "label": "Validation regex" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateAssemblyName", - "label": "Validate Assembly Name", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateShaderName", - "label": "ValidateShaderName", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "text", - "key": "regex", - "label": "Validation regex" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ValidateMeshHasOverlappingUVs", - "label": "ValidateMeshHasOverlappingUVs", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - } - ] - }, { - "type": "raw-json", - "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true - } - ] + "children": [{ + "type": "dict", + "collapsable": true, + "key": "maya", + "label": "Maya", + "children": [ + + { + "type": "schema", + "name": "2_maya_capture" + }, + { + "type": "schema", + "name": "2_maya_plugins" + }, + { + "type": "schema", + "name": "2_maya_workfiles" + } + ] + }] }, { "type": "dict", "collapsable": true, "key": "nuke", "label": "Nuke", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Create plugins", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": false, + "key": "CreateWriteRender", + "label": "CreateWriteRender", + "is_group": true, + "children": [{ + "type": "text", + "key": "fpath_template", + "label": "Path template" + }] + }, { + "type": "dict", + "collapsable": false, + "key": "CreateWritePrerender", + "label": "CreateWritePrerender", + "is_group": true, + "children": [{ + "type": "text", + "key": "fpath_template", + "label": "Path template" + }] + }] + }, { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "create", - "label": "Create plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": false, - "key": "CreateWriteRender", - "label": "CreateWriteRender", - "is_group": true, - "children": [ - { - "type": "text", - "key": "fpath_template", - "label": "Path template" - } - ] - }, { - "type": "dict", - "collapsable": false, - "key": "CreateWritePrerender", - "label": "CreateWritePrerender", - "is_group": true, - "children": [ - { - "type": "text", - "key": "fpath_template", - "label": "Path template" - } - ] - } - ] + "checkbox_key": "enabled", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "nodes", + "label": "Nodes" + }] }, { "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractThumbnail", - "label": "ExtractThumbnail", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "raw-json", - "key": "nodes", - "label": "Nodes" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ValidateNukeWriteKnobs", - "label": "ValidateNukeWriteKnobs", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "raw-json", - "key": "knobs", - "label": "Knobs" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewDataLut", - "label": "ExtractReviewDataLut", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewDataMov", - "label": "ExtractReviewDataMov", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "boolean", - "key": "viewer_lut_raw", - "label": "Viewer LUT raw" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "ExtractSlateFrame", - "label": "ExtractSlateFrame", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "viewer_lut_raw", - "label": "Viewer LUT raw" - } - ] - }, { - "type": "dict", - "collapsable": true, - "key": "NukeSubmitDeadline", - "label": "NukeSubmitDeadline", - "is_group": true, - "children": [ - { - "type": "number", - "key": "deadline_priority", - "label": "deadline_priority" - }, { - "type": "text", - "key": "deadline_pool", - "label": "deadline_pool" - }, { - "type": "text", - "key": "deadline_pool_secondary", - "label": "deadline_pool_secondary" - }, { - "type": "number", - "key": "deadline_chunk_size", - "label": "deadline_chunk_size" - } - ] - } - ] + "checkbox_key": "enabled", + "key": "ValidateNukeWriteKnobs", + "label": "ValidateNukeWriteKnobs", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "knobs", + "label": "Knobs" + }] }, { - "type": "raw-json", - "key": "workfile_build", - "label": "Workfile Build logic", - "is_file": true - } - ] + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataLut", + "label": "ExtractReviewDataLut", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataMov", + "label": "ExtractReviewDataMov", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractSlateFrame", + "label": "ExtractSlateFrame", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + }] + }, { + "type": "dict", + "collapsable": true, + "key": "NukeSubmitDeadline", + "label": "NukeSubmitDeadline", + "is_group": true, + "children": [{ + "type": "number", + "key": "deadline_priority", + "label": "deadline_priority" + }, { + "type": "text", + "key": "deadline_pool", + "label": "deadline_pool" + }, { + "type": "text", + "key": "deadline_pool_secondary", + "label": "deadline_pool_secondary" + }, { + "type": "number", + "key": "deadline_chunk_size", + "label": "deadline_chunk_size" + }] + }] + }, { + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true + }] }, { "type": "dict", "collapsable": true, "key": "nukestudio", "label": "NukeStudio", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [{ "type": "dict", "collapsable": true, - "key": "publish", - "label": "Publish plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "CollectInstanceVersion", - "label": "Collect Instance Version", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - } - ] - }, { - "type": "dict", - "collapsable": true, - "checkbox_key": "enabled", - "key": "ExtractReviewCutUpVideo", - "label": "Extract Review Cut Up Video", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, { - "type": "list", - "object_type": "text", - "key": "tags_addition", - "label": "Tags addition" - } - ] - } - ] - } - ] + "checkbox_key": "enabled", + "key": "CollectInstanceVersion", + "label": "Collect Instance Version", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewCutUpVideo", + "label": "Extract Review Cut Up Video", + "is_group": true, + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "list", + "object_type": "text", + "key": "tags_addition", + "label": "Tags addition" + }] + }] + }] }, { "type": "dict", "collapsable": true, "key": "resolve", "label": "DaVinci Resolve", - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "create", - "label": "Creator plugins", - "is_file": true, - "children": [ - { - "type": "dict", - "collapsable": true, - "key": "CreateShotClip", - "label": "Create Shot Clip", - "is_group": true, - "children": [ - { - "type": "text", - "key": "clipName", - "label": "Clip name template" - }, { - "type": "text", - "key": "folder", - "label": "Folder" - }, { - "type": "number", - "key": "steps", - "label": "Steps" - } - ] - } + "children": [{ + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "is_file": true, + "children": [{ + "type": "dict", + "collapsable": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [{ + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, { + "type": "text", + "key": "folder", + "label": "Folder" + }, { + "type": "number", + "key": "steps", + "label": "Steps" + }] + } - ] - } - ] + ] + }] }, { "type": "dict", "collapsable": true, "key": "standalonepublisher", "label": "Standalone Publisher", - "children": [ - { + "children": [{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "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" - } - ] - } - ] - } - ] - } - ] + "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" + } + ] + }] + }] + }] } ] } diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json new file mode 100644 index 0000000000..836ad90404 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_capture.json @@ -0,0 +1,522 @@ +{ + "type": "dict", + "collapsable": true, + "key": "maya_capture", + "label": "Maya Capture settings", + "is_file": true, + "children": [ + { + "type": "dict-invisible", + "key": "Codec", + "label": "Codec", + "collapsable": false, + "children": [ + { + "type": "label", + "label": "Codec" + }, + { + "type": "text", + "key": "compression", + "label": "Compression type" + }, { + "type": "text", + "key": "format", + "label": "Data format" + }, { + "type": "number", + "key": "quality", + "label": "Quality", + "decimal": 0, + "minimum": 0, + "maximum": 100 + }, + + { + "type": "splitter" + } + ] + }, { + "type": "dict-invisible", + "key": "Display Options", + "label": "Display Options", + "collapsable": false, + "children": [ + { + "type": "label", + "label": "Display Options" + }, + { + "type": "list-strict", + "key": "background", + "label": "Background Color: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, { + "type": "list-strict", + "key": "backgroundBottom", + "label": "Background Bottom: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, { + "type": "list-strict", + "key": "backgroundTop", + "label": "Background Top: ", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + }, { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 3 + } + ] + }, { + "type": "boolean", + "key": "override_display", + "label": "Override display options" + } + ] + }, + { + "type": "splitter" + } , + { + "type": "dict-invisible", + "collapsable": true, + "key": "Generic", + "label": "Generic", + "children": [ + { + "type": "label", + "label": "Generic" + }, + { + "type": "boolean", + "key": "isolate_view", + "label": " Isolate view" + },{ + "type": "boolean", + "key": "off_screen", + "label": " Off Screen" + } + ] + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "IO", + "label": "IO", + "children": [ + { + "type": "label", + "label": "IO" + }, + { + "type": "text", + "key": "name", + "label": "Name" + },{ + "type": "boolean", + "key": "open_finished", + "label": "Open finished" + },{ + "type": "boolean", + "key": "raw_frame_numbers", + "label": "Raw frame numbers" + },{ + "type": "list", + "key": "recent_playblasts", + "label": "Recent Playblasts", + "object_type": "text" + },{ + "type": "boolean", + "key": "save_file", + "label": "Save file" + } + ] + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "PanZoom", + "label": "Pan Zoom", + "children": [ + { + "type": "boolean", + "key": "pan_zoom", + "label": " Pan Zoom" + } + ] + }, + { + "type": "splitter" + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "Renderer", + "label": "Renderer", + "children": [ + + { + "type": "label", + "label": "Renderer" + }, + { + "type": "text", + "key": "rendererName", + "label": " Renderer name" + } + ] + },{ + "type": "dict-invisible", + "collapsable": true, + "key": "Resolution", + "label": "Resolution", + "children": [ + + { + "type": "splitter" + }, + { + "type": "label", + "label": "Resolution" + }, + { + "type": "number", + "key": "width", + "label": " Width", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + },{ + "type": "number", + "key": "height", + "label": "Height", + "decimal": 0, + "minimum": 0, + "maximum": 99999 + },{ + "type": "number", + "key": "percent", + "label": "percent", + "decimal": 1, + "minimum": 0, + "maximum": 200 + },{ + "type": "text", + "key": "mode", + "label": "Mode" + } + ] + }, + { + "type": "splitter" + }, + { + "type": "dict-invisible", + "collapsable": true, + "key": "Time Range", + "label": "Time Range", + "children": [ + { + "type": "label", + "label": "Time Range" + }, + { + "type": "number", + "key": "start_frame", + "label": " Start frame", + "decimal": 0, + "minimum": 0, + "maximum": 999999 + },{ + "type": "number", + "key": "end_frame", + "label": "End frame", + "decimal": 0, + "minimum": 0, + "maximum": 999999 + },{ + "type": "text", + "key": "frame", + "label": "Frame" + },{ + "type": "text", + "key": "time", + "label": "Time" + } + ] + },{ + "type": "dict", + "collapsable": true, + "key": "Viewport Options", + "label": "Viewport Options", + "children": [ + { + "type": "boolean", + "key": "cameras", + "label": "cameras" + },{ + "type": "boolean", + "key": "clipGhosts", + "label": "clipGhosts" + },{ + "type": "boolean", + "key": "controlVertices", + "label": "controlVertices" + },{ + "type": "boolean", + "key": "deformers", + "label": "deformers" + },{ + "type": "boolean", + "key": "dimensions", + "label": "dimensions" + },{ + "type": "number", + "key": "displayLights", + "label": "displayLights", + "decimal": 0, + "minimum": 0, + "maximum": 10 + },{ + "type": "boolean", + "key": "dynamicConstraints", + "label": "dynamicConstraints" + },{ + "type": "boolean", + "key": "dynamics", + "label": "dynamics" + },{ + "type": "boolean", + "key": "fluids", + "label": "fluids" + },{ + "type": "boolean", + "key": "follicles", + "label": "follicles" + },{ + "type": "boolean", + "key": "gpuCacheDisplayFilter", + "label": "gpuCacheDisplayFilter" + },{ + "type": "boolean", + "key": "greasePencils", + "label": "greasePencils" + },{ + "type": "boolean", + "key": "grid", + "label": "grid" + },{ + "type": "boolean", + "key": "hairSystems", + "label": "hairSystems" + },{ + "type": "boolean", + "key": "handles", + "label": "handles" + },{ + "type": "boolean", + "key": "high_quality", + "label": "high_quality" + },{ + "type": "boolean", + "key": "hud", + "label": "hud" + },{ + "type": "boolean", + "key": "hulls", + "label": "hulls" + },{ + "type": "boolean", + "key": "ikHandles", + "label": "ikHandles" + },{ + "type": "boolean", + "key": "imagePlane", + "label": "imagePlane" + },{ + "type": "boolean", + "key": "joints", + "label": "joints" + },{ + "type": "boolean", + "key": "lights", + "label": "lights" + },{ + "type": "boolean", + "key": "locators", + "label": "locators" + },{ + "type": "boolean", + "key": "manipulators", + "label": "manipulators" + },{ + "type": "boolean", + "key": "motionTrails", + "label": "motionTrails" + },{ + "type": "boolean", + "key": "nCloths", + "label": "nCloths" + },{ + "type": "boolean", + "key": "nParticles", + "label": "nParticles" + },{ + "type": "boolean", + "key": "nRigids", + "label": "nRigids" + },{ + "type": "boolean", + "key": "nurbsCurves", + "label": "nurbsCurves" + },{ + "type": "boolean", + "key": "nurbsSurfaces", + "label": "nurbsSurfaces" + },{ + "type": "boolean", + "key": "override_viewport_options", + "label": "override_viewport_options" + },{ + "type": "boolean", + "key": "particleInstancers", + "label": "particleInstancers" + },{ + "type": "boolean", + "key": "pivots", + "label": "pivots" + },{ + "type": "boolean", + "key": "planes", + "label": "planes" + },{ + "type": "boolean", + "key": "pluginShapes", + "label": "pluginShapes" + },{ + "type": "boolean", + "key": "polymeshes", + "label": "polymeshes" + },{ + "type": "boolean", + "key": "shadows", + "label": "shadows" + },{ + "type": "boolean", + "key": "strokes", + "label": "strokes" + },{ + "type": "boolean", + "key": "subdivSurfaces", + "label": "subdivSurfaces" + },{ + "type": "boolean", + "key": "textures", + "label": "textures" + },{ + "type": "boolean", + "key": "twoSidedLighting", + "label": "twoSidedLighting" + } + ] + },{ + "type": "dict", + "collapsable": true, + "key": "Camera Options", + "label": "Camera Options", + "children": [ + { + "type": "boolean", + "key": "displayGateMask", + "label": "displayGateMask" + },{ + "type": "boolean", + "key": "displayResolution", + "label": "displayResolution" + },{ + "type": "boolean", + "key": "displayFilmGate", + "label": "displayFilmGate" + },{ + "type": "boolean", + "key": "displayFieldChart", + "label": "displayFieldChart" + },{ + "type": "boolean", + "key": "displaySafeAction", + "label": "displaySafeAction" + },{ + "type": "boolean", + "key": "displaySafeTitle", + "label": "displaySafeTitle" + },{ + "type": "boolean", + "key": "displayFilmPivot", + "label": "displayFilmPivot" + },{ + "type": "boolean", + "key": "displayFilmOrigin", + "label": "displayFilmOrigin" + },{ + "type": "number", + "key": "overscan", + "label": "overscan", + "decimal": 1, + "minimum": 0, + "maximum": 10 + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json new file mode 100644 index 0000000000..7ba9608610 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_plugins.json @@ -0,0 +1,90 @@ +{ + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ValidateModelName", + "label": "Validate Model Name", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Path to material file defining list of material names to check. This is material name per line simple text file.
It will be checked against named group shader in your Validation regex.

For example:
^.*(?P=<shader>.+)_GEO

" + }, + { + "type": "path-widget", + "key": "material_file", + "label": "Material File", + "multiplatform": true, + "multipath": false + }, + { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateAssemblyName", + "label": "Validate Assembly Name", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateShaderName", + "label": "ValidateShaderName", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "label", + "label": "Shader name regex can use named capture group asset to validate against current asset name.

Example:
^.*(?P=<asset>.+)_SHD

" + + }, { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateMeshHasOverlappingUVs", + "label": "ValidateMeshHasOverlappingUVs", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + ] + } diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json new file mode 100644 index 0000000000..bae4d32abd --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/2_maya_workfiles.json @@ -0,0 +1,6 @@ +{ + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json index c5f229fc2f..eb7d707f6a 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json @@ -7,7 +7,7 @@ "key": "global", "children": [{ "type": "schema", - "name": "1_intents_gui_schema" + "name": "1_general_gui_schema" },{ "type": "schema", "name": "1_modules_gui_schema" 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 3427f98253..6b73fc3f8c 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 @@ -3,137 +3,82 @@ "type": "dict", "label": "Applications", "collapsable": true, - "is_group": true, "is_file": true, - "children": [ + "children": [{ + "type": "schema", + "name": "system_maya_schema" + }, { - "type": "boolean", - "key": "blender_2.80", - "label": "Blender 2.80" - }, { - "type": "boolean", - "key": "blender_2.81", - "label": "Blender 2.81" - }, { - "type": "boolean", - "key": "blender_2.82", - "label": "Blender 2.82" - }, { - "type": "boolean", - "key": "blender_2.83", - "label": "Blender 2.83" - }, { - "type": "boolean", - "key": "celaction_local", - "label": "Celaction Local" - }, { - "type": "boolean", - "key": "celaction_remote", - "label": "Celaction Remote" - }, { - "type": "boolean", - "key": "harmony_17", - "label": "Harmony 17" - }, { - "type": "boolean", - "key": "maya_2017", - "label": "Autodest Maya 2017" - }, { - "type": "boolean", - "key": "maya_2018", - "label": "Autodest Maya 2018" - }, { - "type": "boolean", - "key": "maya_2019", - "label": "Autodest Maya 2019" - }, { - "type": "boolean", - "key": "maya_2020", - "label": "Autodest Maya 2020" - }, { - "key": "nuke_10.0", - "type": "boolean", - "label": "Nuke 10.0" - }, { - "type": "boolean", - "key": "nuke_11.2", - "label": "Nuke 11.2" - }, { - "type": "boolean", - "key": "nuke_11.3", - "label": "Nuke 11.3" - }, { - "type": "boolean", - "key": "nuke_12.0", - "label": "Nuke 12.0" - }, { - "type": "boolean", - "key": "nukex_10.0", - "label": "NukeX 10.0" - }, { - "type": "boolean", - "key": "nukex_11.2", - "label": "NukeX 11.2" - }, { - "type": "boolean", - "key": "nukex_11.3", - "label": "NukeX 11.3" - }, { - "type": "boolean", - "key": "nukex_12.0", - "label": "NukeX 12.0" - }, { - "type": "boolean", - "key": "nukestudio_10.0", - "label": "NukeStudio 10.0" - }, { - "type": "boolean", - "key": "nukestudio_11.2", - "label": "NukeStudio 11.2" - }, { - "type": "boolean", - "key": "nukestudio_11.3", - "label": "NukeStudio 11.3" - }, { - "type": "boolean", - "key": "nukestudio_12.0", - "label": "NukeStudio 12.0" - }, { - "type": "boolean", - "key": "houdini_16", - "label": "Houdini 16" - }, { - "type": "boolean", - "key": "houdini_16.5", - "label": "Houdini 16.5" - }, { - "type": "boolean", - "key": "houdini_17", - "label": "Houdini 17" - }, { - "type": "boolean", - "key": "houdini_18", - "label": "Houdini 18" - }, { - "type": "boolean", - "key": "premiere_2019", - "label": "Premiere 2019" - }, { - "type": "boolean", - "key": "premiere_2020", - "label": "Premiere 2020" - }, { - "type": "boolean", - "key": "resolve_16", - "label": "BM DaVinci Resolve 16" - }, { - "type": "boolean", - "key": "storyboardpro_7", - "label": "Storyboard Pro 7" - }, { - "type": "boolean", - "key": "unreal_4.24", - "label": "Unreal Editor 4.24" + "type": "schema_template", + "name": "system_nuke_template", + "template_data": { + "nuke_type": "nuke", + "nuke_label": "Nuke" + } + }, + { + "type": "schema_template", + "name": "system_nuke_template", + "template_data": { + "nuke_type": "nukex", + "nuke_label": "Nuke X" + } + }, + { + "type": "schema_template", + "name": "system_nuke_template", + "template_data": { + "nuke_type": "nukestudio", + "nuke_label": "Nuke Studio" + } + }, + { + "type": "schema_template", + "name": "system_nuke_template", + "template_data": { + "nuke_type": "hiero", + "nuke_label": "Hiero" + } + }, + { + "type": "schema", + "name": "system_fusion_schema" + }, + { + "type": "schema", + "name": "system_resolve_schema" + }, + { + "type": "schema", + "name": "system_houdini_schema" + }, + { + "type": "schema", + "name": "system_blender_schema" + }, + { + "type": "schema", + "name": "system_harmony_schema" + }, + { + "type": "schema", + "name": "system_photoshop_schema" + }, + { + "type": "schema", + "name": "system_celaction_schema" + }, + { + "type": "schema", + "name": "system_unreal_schema" + }, + { + "type": "schema", + "name": "system_shell_schema" + }, + { + "type": "schema", + "name": "system_djv_schema" } ] } diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_general_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_general_gui_schema.json new file mode 100644 index 0000000000..15252ab39d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_general_gui_schema.json @@ -0,0 +1,39 @@ +{ + "key": "general", + "type": "dict", + "label": "General", + "collapsable": true, + "is_file": true, + "children": [{ + "key": "studio_name", + "type": "text", + "label": "Studio Name" + }, { + "key": "studio_code", + "type": "text", + "label": "Studio Short Code" + }, { + "type": "splitter" + }, { + "key": "project_plugins", + "type": "path-widget", + "label": "Additional Project Plugins Path", + "multiplatform": true, + "multipath": false + }, { + "key": "studio_soft", + "type": "path-widget", + "label": "Studio Software Location", + "multiplatform": true, + "multipath": false + }, { + "type": "splitter" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "global" + } + ] +} 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 deleted file mode 100644 index 7f71da26cd..0000000000 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "key": "general", - "type": "dict", - "label": "General", - "collapsable": true, - "is_file": true, - "children": [{ - "key": "studio_name", - "type": "text", - "label": "Studio Name" - },{ - "key": "studio_code", - "type": "text", - "label": "Studio Short Code" - } -]} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json index 7b3d8cdd13..937eea4097 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_modules_gui_schema.json @@ -1,283 +1,301 @@ { - "key": "modules", - "type": "dict", - "label": "Modules", - "collapsable": true, - "is_file": true, - "children": [{ - "type": "dict", - "key": "Avalon", - "label": "Avalon", - "collapsable": true, - "children": [ - { - "type": "text", - "key": "AVALON_MONGO", - "label": "Avalon Mongo URL" - }, - { - "type": "text", - "key": "AVALON_DB_DATA", - "label": "Avalon Mongo Data Location" - }, - { - "type": "text", - "key": "AVALON_THUMBNAIL_ROOT", - "label": "Thumbnail Storage Location" - } - ] - },{ + "key": "modules", + "type": "dict", + "label": "Modules", + "collapsable": true, + "is_file": true, + "children": [{ + "type": "dict", + "key": "Avalon", + "label": "Avalon", + "collapsable": true, + "children": [{ + "type": "text", + "key": "AVALON_MONGO", + "label": "Avalon Mongo URL" + }, + { + "type": "text", + "key": "AVALON_DB_DATA", + "label": "Avalon Mongo Data Location" + }, + { + "type": "text", + "key": "AVALON_THUMBNAIL_ROOT", + "label": "Thumbnail Storage Location" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "avalon" + } + ] + }, { "type": "dict", "key": "Ftrack", "label": "Ftrack", "collapsable": true, "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "ftrack_server", + "label": "Server" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Additional Ftrack paths" + }, + { + "type": "list", + "key": "ftrack_actions_path", + "label": "Action paths", + "object_type": "text" + }, + { + "type": "list", + "key": "ftrack_events_path", + "label": "Event paths", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "type": "label", + "label": "Ftrack event server advanced settings" + }, + { + "type": "text", + "key": "FTRACK_EVENTS_MONGO_DB", + "label": "Event Mongo DB" + }, + { + "type": "text", + "key": "FTRACK_EVENTS_MONGO_COL", + "label": "Events Mongo Collection" + }, + { + "type": "dict", + "key": "sync_to_avalon", + "label": "Sync to avalon", + "children": [{ + "type": "list", + "key": "statuses_name_change", + "label": "Status name change", + "object_type": { + "type": "text", + "multiline": false + } + }] + }, + { + "type": "dict-modifiable", + "key": "status_version_to_task", + "label": "Version to Task status mapping", + "object_type": "text" + }, + { + "type": "dict-modifiable", + "key": "status_update", + "label": "Status Updates", + "object_type": { + "type": "list", + "object_type": "text" + } + }, + { + "key": "intent", + "type": "dict-invisible", + "children": [{ + "type": "dict-modifiable", + "object_type": "text", + "key": "items", + "label": "Intent Key/Label" + }, + { + "key": "default", + "type": "text", + "label": "Default Intent" + } + ] + }, + { + "type": "splitter" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "ftrack" + } + ] + }, { + "type": "dict", + "key": "Rest Api", + "label": "Rest Api", + "collapsable": true, + "children": [{ + "type": "number", + "key": "default_port", + "label": "Default Port", + "minimum": 1, + "maximum": 65535 + }, + { + "type": "list", + "key": "exclude_ports", + "label": "Exclude ports", + "object_type": { + "type": "number", + "minimum": 1, + "maximum": 65535 + } + } + ] + }, { + "type": "dict", + "key": "Timers Manager", + "label": "Timers Manager", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "number", + "decimal": 2, + "key": "full_time", + "label": "Max idle time" + }, { + "type": "number", + "decimal": 2, + "key": "message_time", + "label": "When dialog will show" + } + ] + }, { + "type": "dict", + "key": "Clockify", + "label": "Clockify", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "workspace_name", + "label": "Workspace name" + } + ] + }, { + "type": "dict", + "key": "Deadline", + "label": "Deadline", + "collapsable": true, + "checkbox_key": "enabled", "children": [{ "type": "boolean", "key": "enabled", "label": "Enabled" - }, - { - "type": "text", - "key": "ftrack_server", - "label": "Server" - }, - { - "type": "label", - "label": "Additional Ftrack paths" - }, - { - "type": "list", - "key": "ftrack_actions_path", - "label": "Action paths", - "object_type": "text" - }, - { - "type": "list", - "key": "ftrack_events_path", - "label": "Event paths", - "object_type": "text" - }, - { - "type": "label", - "label": "Ftrack event server advanced settings" - }, - { - "type": "text", - "key": "FTRACK_EVENTS_MONGO_DB", - "label": "Event Mongo DB" - }, - { - "type": "text", - "key": "FTRACK_EVENTS_MONGO_COL", - "label": "Events Mongo Collection" - }, - { - "type": "dict", - "key": "sync_to_avalon", - "label": "Sync to avalon", - "children": [{ - "type": "list", - "key": "statuses_name_change", - "label": "Status name change", - "object_type": "text", - "input_modifiers": { - "multiline": false - } - }] - }, - { - "type": "dict-modifiable", - "key": "status_version_to_task", - "label": "Version to Task status mapping", - "object_type": "text" - }, - { - "type": "dict-modifiable", - "key": "status_update", - "label": "Status Updates", - "object_type": "list", - "input_modifiers": { - "object_type": "text" - } - }, - { - "key": "intent", - "type": "dict-invisible", - "children": [ - { - "type": "dict-modifiable", - "object_type": "text", - "key": "items", - "label": "Intent Key/Label" - }, - { - "key": "default", - "type": "text", - "label": "Defautl Intent" - } - ] - } - ] - }, { - "type": "dict", - "key": "Rest Api", - "label": "Rest Api", - "collapsable": true, - "children": [{ - "type": "number", - "key": "default_port", - "label": "Default Port", - "minimum": 1, - "maximum": 65535 - }, - { - "type": "list", - "object_type": "number", - "key": "exclude_ports", - "label": "Exclude ports", - "input_modifiers": { - "minimum": 1, - "maximum": 65535 - } - } - ] - }, { - "type": "dict", - "key": "Timers Manager", - "label": "Timers Manager", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "number", - "decimal": 2, - "key": "full_time", - "label": "Max idle time" }, { - "type": "number", - "decimal": 2, - "key": "message_time", - "label": "When dialog will show" - } - ] - }, { - "type": "dict", - "key": "Clockify", - "label": "Clockify", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "workspace_name", - "label": "Workspace name" - } - ] - }, { - "type": "dict", - "key": "Deadline", - "label": "Deadline", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - },{ - "type": "text", - "key": "DEADLINE_REST_URL", - "label": "Deadline Resl URL" + "type": "text", + "key": "DEADLINE_REST_URL", + "label": "Deadline Resl URL" }] }, { - "type": "dict", - "key": "Muster", - "label": "Muster", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" - },{ - "type": "text", - "key": "MUSTER_REST_URL", - "label": "Muster Resl URL" - },{ - "type": "dict-modifiable", - "object_type": "number", - "input_modifiers": { - "minimum": 0, - "maximum": 300 - }, - "is_group": true, - "key": "templates_mapping", - "label": "Templates mapping", - "is_file": true - }] - }, { - "type": "dict", - "key": "Logging", - "label": "Logging", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "type": "dict", + "key": "Muster", + "label": "Muster", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "MUSTER_REST_URL", + "label": "Muster Resl URL" + }, { + "type": "dict-modifiable", + "object_type": { + "type": "number", + "minimum": 0, + "maximum": 300 + }, + "is_group": true, + "key": "templates_mapping", + "label": "Templates mapping", + "is_file": true }] }, { - "type": "dict", - "key": "Adobe Communicator", - "label": "Adobe Communicator", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "type": "dict", + "key": "Logging", + "label": "Logging", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" }] }, { - "type": "dict", - "key": "User setting", - "label": "User setting", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "type": "dict", + "key": "Adobe Communicator", + "label": "Adobe Communicator", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" }] }, { - "type": "dict", - "key": "Standalone Publish", - "label": "Standalone Publish", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "type": "dict", + "key": "User setting", + "label": "User setting", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" }] }, { - "type": "dict", - "key": "Idle Manager", - "label": "Idle Manager", - "collapsable": true, - "checkbox_key": "enabled", - "children": [{ - "type": "boolean", - "key": "enabled", - "label": "Enabled" + "type": "dict", + "key": "Standalone Publish", + "label": "Standalone Publish", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" }] - } - ] + }, { + "type": "dict", + "key": "Idle Manager", + "label": "Idle Manager", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }] + }] } diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json similarity index 86% rename from pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json rename to pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json index 0578968508..7612e54116 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json @@ -5,10 +5,63 @@ "is_file": true, "children": [ { + "type": "dict", + "key": "schema_template_exaples", + "label": "Schema template examples", + "children": [ + { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Maya 2019", + "host_name": "maya_2019", + "multipath_executables": false + } + }, { + "type": "schema_template", + "name": "example_template", + "template_data": { + "host_label": "Maya 2020", + "host_name": "maya_2020" + } + } + ] + }, { + "key": "env_group_test", + "label": "EnvGroup Test", + "type": "dict", + "children": [ + { + "key": "key_to_store_in_system_settings", + "label": "Testing environment group", + "type": "raw-json", + "env_group_key": "test_group" + } + ] + }, { "key": "dict_wrapper", "type": "dict-invisible", "children": [ { + "type": "enum", + "key": "test_enum_singleselection", + "label": "Enum Single Selection", + "enum_items": [ + {"value_1": "Label 1"}, + {"value_2": "Label 2"}, + {"value_3": "Label 3"} + ] + }, { + "type": "enum", + "key": "test_enum_multiselection", + "label": "Enum Multi Selection", + "multiselection": true, + "enum_items": [ + {"value_1": "Label 1"}, + {"value_2": "Label 2"}, + {"value_3": "Label 3"} + ] + }, { "type": "boolean", "key": "bool", "label": "Boolean checkbox" @@ -48,16 +101,16 @@ "type": "list", "key": "list_item_of_multiline_texts", "label": "List of multiline texts", - "object_type": "text", - "input_modifiers": { + "object_type": { + "type": "text", "multiline": true } }, { "type": "list", "key": "list_item_of_floats", "label": "List of floats", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "decimal": 3, "minimum": 1000, "maximum": 2000 @@ -66,8 +119,8 @@ "type": "dict-modifiable", "key": "modifiable_dict_of_integers", "label": "Modifiable dict of integers", - "object_type": "number", - "input_modifiers": { + "object_type": { + "type": "number", "decimal": 0, "minimum": 10, "maximum": 100 @@ -194,8 +247,8 @@ "type": "list", "key": "dict_item", "label": "DictItem in List", - "object_type": "dict-item", - "input_modifiers": { + "object_type": { + "type": "dict-item", "children": [ { "key": "families", diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/example_template.json b/pype/tools/settings/settings/gui_schemas/system_schema/example_template.json new file mode 100644 index 0000000000..48a3c955b9 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/example_template.json @@ -0,0 +1,18 @@ +[ + { + "__default_values__": { + "multipath_executables": true + } + }, { + "type": "raw-json", + "label": "{host_label} Environments", + "key": "{host_name}_environments", + "env_group_key": "{host_name}" + }, { + "type": "path-widget", + "key": "{host_name}_executables", + "label": "{host_label} - Full paths to executables", + "multiplatform": "{multipath_executables}", + "multipath": true + } +] diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_blender_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_blender_schema.json new file mode 100644 index 0000000000..8600d8de8a --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_blender_schema.json @@ -0,0 +1,35 @@ +{ + "type": "dict", + "key": "blender", + "label": "Blender", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "blender" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "2.90", + "host_name": "blender" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "2.83", + "host_name": "blender" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_celaction_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_celaction_schema.json new file mode 100644 index 0000000000..36addab00d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_celaction_schema.json @@ -0,0 +1,39 @@ +{ + "type": "dict", + "key": "celaction", + "label": "CelAction2D", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "celaction" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "Local", + "host_name": "celation", + "multiplatform": false, + "multipath_executables": false + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "Publish", + "host_name": "celation", + "multiplatform": false, + "multipath_executables": false + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_djv_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_djv_schema.json new file mode 100644 index 0000000000..704b13443d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_djv_schema.json @@ -0,0 +1,27 @@ +{ + "type": "dict", + "key": "djvview", + "label": "DJV View", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "djvview" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "1.1", + "host_name": "djvview" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_fusion_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_fusion_schema.json new file mode 100644 index 0000000000..b3dc79ef97 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_fusion_schema.json @@ -0,0 +1,35 @@ +{ + "type": "dict", + "key": "fusion", + "label": "Blackmagic Fusion", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "fusion" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "16", + "host_name": "fusion" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "9", + "host_name": "fusion" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_harmony_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_harmony_schema.json new file mode 100644 index 0000000000..10cb929fc4 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_harmony_schema.json @@ -0,0 +1,51 @@ +{ + "type": "dict", + "key": "harmony", + "label": "Toon Boom Harmony", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "harmony" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "20", + "host_name": "harmony" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "19", + "host_name": "harmony" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "18", + "host_name": "harmony" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "17", + "host_name": "harmony" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_host_template.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_host_template.json new file mode 100644 index 0000000000..b39d6ac79d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_host_template.json @@ -0,0 +1,33 @@ +[{ + "__default_values__": { + "multipath_executables": true, + "multiplatform": true + } + }, + { + "type": "dict", + "key": "{host_name}_{host_version}", + "label": "{host_version}", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "path-widget", + "key": "{host_name}_executables", + "label": "Executables", + "multiplatform": "{multiplatform}", + "multipath": "{multipath_executables}" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "{host_name}_{host_version}" + } + ] + } +] diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_houdini_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_houdini_schema.json new file mode 100644 index 0000000000..36c6d9fcb2 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_houdini_schema.json @@ -0,0 +1,35 @@ +{ + "type": "dict", + "key": "houdini", + "label": "SideFX Houdini", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "houdini" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "18", + "host_name": "houdini" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "17", + "host_name": "houdini" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_maya_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_maya_schema.json new file mode 100644 index 0000000000..710b62a9cc --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_maya_schema.json @@ -0,0 +1,43 @@ +{ + "type": "dict", + "key": "maya", + "label": "Autodesk Maya", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "maya" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "2020", + "host_name": "maya" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "2019", + "host_name": "maya" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "2018", + "host_name": "maya" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_nuke_template.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_nuke_template.json new file mode 100644 index 0000000000..22b2e0c4df --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_nuke_template.json @@ -0,0 +1,47 @@ +[{ + "type": "dict", + "key": "{nuke_type}", + "label": "Foundry {nuke_label}", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "{nuke_type}" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "12.0", + "host_name": "{nuke_type}", + "multipath_executables": true + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "11.3", + "host_name": "{nuke_type}", + "multipath_executables": true + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "11.2", + "host_name": "{nuke_type}", + "multipath_executables": true + } + } + ] +} +] diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_photoshop_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_photoshop_schema.json new file mode 100644 index 0000000000..c1062045e7 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_photoshop_schema.json @@ -0,0 +1,27 @@ +{ + "type": "dict", + "key": "photoshop", + "label": "Adobe Photoshop", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "photoshop" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "2020", + "host_name": "photoshop" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_resolve_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_resolve_schema.json new file mode 100644 index 0000000000..364be8208d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_resolve_schema.json @@ -0,0 +1,27 @@ +{ + "type": "dict", + "key": "resolve", + "label": "Blackmagic DaVinci Resolve", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "resolve" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "16", + "host_name": "resolve" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_shell_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_shell_schema.json new file mode 100644 index 0000000000..22955abc46 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_shell_schema.json @@ -0,0 +1,43 @@ +{ + "type": "dict", + "key": "shell", + "label": "Shell", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "shell" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "Python 3.7", + "host_name": "python" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "Python 2.7", + "host_name": "python" + } + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "Terminal", + "host_name": "terminal" + } + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_unreal_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_unreal_schema.json new file mode 100644 index 0000000000..e0408f9a36 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/host_settings/system_unreal_schema.json @@ -0,0 +1,27 @@ +{ + "type": "dict", + "key": "unreal", + "label": "Unreal Editor", + "collapsable": true, + "checkbox_key": "enabled", + "children": [{ + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "unreal" + }, + { + "type": "schema_template", + "name": "system_host_template", + "template_data": { + "host_version": "4.24", + "host_name": "unreal" + } + } + ] +} diff --git a/pype/tools/settings/settings/style/style.css b/pype/tools/settings/settings/style/style.css index 3f648abef8..dcc7a5effe 100644 --- a/pype/tools/settings/settings/style/style.css +++ b/pype/tools/settings/settings/style/style.css @@ -38,6 +38,18 @@ QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QPlainTextEdit:d QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus { border: 1px solid #ffffff; } + +QComboBox { + border: 1px solid #aaaaaa; + border-radius: 3px; + padding: 2px 2px 4px 4px; + background: #1d272f; +} + +QComboBox QAbstractItemView::item { + padding: 3px; +} + QToolButton { background: transparent; } @@ -102,6 +114,10 @@ QPushButton[btn-type="expand-toggle"] { font-weight: bold; } +#MultiSelectionComboBox { + font-size: 12px; +} + #DictKey[state="studio"] {border-color: #bfccd6;} #DictKey[state="modified"] {border-color: #137cbd;} #DictKey[state="overriden"] {border-color: #00f;} @@ -152,8 +168,10 @@ QPushButton[btn-type="expand-toggle"] { background: #141a1f; } -#DictAsWidgetBody{ +#DictAsWidgetBody { background: transparent; +} +#DictAsWidgetBody[show_borders="1"] { border: 2px solid #cccccc; border-radius: 5px; } diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py index 6d7b3292ce..e1a726187c 100644 --- a/pype/tools/settings/settings/widgets/anatomy_types.py +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -271,9 +271,9 @@ class RootsWidget(QtWidgets.QWidget, SettingObject): ) multiroot_data = { "key": self.key, - "object_type": "path-widget", "expandable": False, - "input_modifiers": { + "object_type": { + "type": "path-widget", "multiplatform": True } } diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py index 423380d54c..7cbe7c2f6f 100644 --- a/pype/tools/settings/settings/widgets/base.py +++ b/pype/tools/settings/settings/widgets/base.py @@ -47,6 +47,7 @@ class SystemWidget(QtWidgets.QWidget): self._ignore_value_changes = False self.input_fields = [] + self.environ_fields = [] scroll_widget = QtWidgets.QScrollArea(self) scroll_widget.setObjectName("GroupWidget") @@ -130,14 +131,18 @@ class SystemWidget(QtWidgets.QWidget): for input_field in self.input_fields: input_field.hierarchical_style_update() + def add_environ_field(self, input_field): + self.environ_fields.append(input_field) + 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.input_fields.clear() + self.environ_fields.clear() + while self.content_layout.count() != 0: + widget = self.content_layout.itemAt(0).widget() + self.content_layout.removeWidget(widget) + widget.deleteLater() self.schema = lib.gui_schema("system_schema", "0_system_gui_schema") self.keys = self.schema.get("keys", []) @@ -214,7 +219,7 @@ class SystemWidget(QtWidgets.QWidget): all_values = _all_values # Skip first key - all_values = all_values["system"] + all_values = lib.convert_gui_data_with_metadata(all_values["system"]) prject_defaults_dir = os.path.join( DEFAULTS_DIR, SYSTEM_SETTINGS_KEY @@ -246,16 +251,19 @@ class SystemWidget(QtWidgets.QWidget): def _update_values(self): self.ignore_value_changes = True - default_values = { + default_values = lib.convert_data_to_gui_data({ "system": default_settings()[SYSTEM_SETTINGS_KEY] - } + }) for input_field in self.input_fields: input_field.update_default_values(default_values) if self._hide_studio_overrides: system_values = lib.NOT_SET else: - system_values = {"system": studio_system_settings()} + system_values = lib.convert_overrides_to_gui_data( + {"system": studio_system_settings()} + ) + for input_field in self.input_fields: input_field.update_studio_values(system_values) @@ -266,7 +274,11 @@ class SystemWidget(QtWidgets.QWidget): klass = lib.TypeToKlass.types.get(item_type) item = klass(child_configuration, self) self.input_fields.append(item) - self.content_layout.addWidget(item) + self.content_layout.addWidget(item, 0) + + # Add spacer to stretch children guis + spacer = QtWidgets.QWidget(self.content_widget) + self.content_layout.addWidget(spacer, 1) class ProjectListView(QtWidgets.QListView): @@ -528,7 +540,11 @@ class ProjectWidget(QtWidgets.QWidget): klass = lib.TypeToKlass.types.get(item_type) item = klass(child_configuration, self) self.input_fields.append(item) - self.content_layout.addWidget(item) + self.content_layout.addWidget(item, 0) + + # Add spacer to stretch children guis + spacer = QtWidgets.QWidget(self.content_widget) + self.content_layout.addWidget(spacer, 1) def _on_project_change(self): project_name = self.project_list_widget.project_name() @@ -722,17 +738,20 @@ class ProjectWidget(QtWidgets.QWidget): def _update_values(self): self.ignore_value_changes = True - default_values = {"project": default_settings()} + default_values = default_values = lib.convert_data_to_gui_data( + {"project": default_settings()} + ) for input_field in self.input_fields: input_field.update_default_values(default_values) if self._hide_studio_overrides: studio_values = lib.NOT_SET else: - studio_values = {"project": { + studio_values = lib.convert_overrides_to_gui_data({"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) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index e589b89c0f..87f2fcd48e 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -1,17 +1,38 @@ import json -import logging import collections from Qt import QtWidgets, QtCore, QtGui from .widgets import ( ExpandingWidget, NumberSpinBox, PathInput, - GridLabelWidget + GridLabelWidget, + ComboBox, + NiceCheckbox ) +from .multiselection_combobox import MultiSelectionComboBox from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET +from pype.api import Logger from avalon.vendor import qtawesome +class InvalidValueType(Exception): + msg_template = "{}" + + def __init__(self, valid_types, invalid_type, key): + msg = "" + if key: + msg += "Key \"{}\". ".format(key) + + joined_types = ", ".join( + [str(valid_type) for valid_type in valid_types] + ) + msg += "Got invalid type \"{}\". Expected: {}".format( + invalid_type, joined_types + ) + self.msg = msg + super(InvalidValueType, self).__init__(msg) + + class SettingObject: """Partially abstract class for Setting's item type workflow.""" # `is_input_type` attribute says if has implemented item type methods @@ -22,11 +43,44 @@ class SettingObject: # 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 + # If item can store environment values + allow_to_environment = False # All item types must have implemented Qt signal which is emitted when # it's or it's children value has changed, value_changed = None # Item will expand to full width in grid layout expand_in_grid = False + # types that are valid for `set_value` method + valid_value_types = tuple() + + def validate_value(self, value): + if not self.valid_value_types: + return + + for valid_type in self.valid_value_types: + if type(value) is valid_type: + return + + key = getattr(self, "key", None) + raise InvalidValueType(self.valid_value_types, type(value), key) + + def merge_metadata(self, current_metadata, new_metadata): + for key, value in new_metadata.items(): + if key not in current_metadata: + current_metadata[key] = value + + elif key == "groups": + current_metadata[key].extend(value) + + elif key == "environments": + for group_key, subvalue in value.items(): + if group_key not in current_metadata[key]: + current_metadata[key][group_key] = [] + current_metadata[key][group_key].extend(subvalue) + + else: + raise KeyError("Unknown metadata key: \"{}\"".format(key)) + return current_metadata def _set_default_attributes(self): """Create and reset attributes required for all item types. @@ -47,6 +101,9 @@ class SettingObject: self._as_widget = False self._is_group = False + # If value should be stored to environments + self._env_group_key = None + self._any_parent_as_widget = None self._any_parent_is_group = None @@ -82,6 +139,20 @@ class SettingObject: self._is_group = input_data.get("is_group", False) # TODO not implemented yet self._is_nullable = input_data.get("is_nullable", False) + self._env_group_key = input_data.get("env_group_key") + + if self.is_environ: + if not self.allow_to_environment: + raise TypeError(( + "Item {} does not allow to store environment values" + ).format(input_data["type"])) + + if self.as_widget: + raise TypeError(( + "Item is used as widget and" + " marked to store environments at the same time." + )) + self.add_environ_field(self) any_parent_as_widget = parent.as_widget if not any_parent_as_widget: @@ -109,7 +180,7 @@ class SettingObject: def log(self): """Auto created logger for debugging.""" if self._log is None: - self._log = logging.getLogger(self.__class__.__name__) + self._log = Logger().get_logger(self.__class__.__name__) return self._log @property @@ -138,6 +209,17 @@ class SettingObject: """ return self._has_studio_override or self._parent.has_studio_override + @property + def is_environ(self): + return self._env_group_key is not None + + @property + def env_group_key(self): + return self._env_group_key + + def add_environ_field(self, input_field): + self._parent.add_environ_field(input_field) + @property def as_widget(self): """Item is used as widget in parent item. @@ -200,7 +282,7 @@ class SettingObject: def is_modified(self): """Has object any changes that require saving.""" if self.any_parent_as_widget: - return self._is_modified + return self._is_modified or self.defaults_not_set if self._is_modified or self.defaults_not_set: return True @@ -267,6 +349,13 @@ class SettingObject: """Output for saving changes or overrides.""" return {self.key: self.item_value()} + def environment_value(self): + raise NotImplementedError( + "{} Method `environment_value` not implemented!".format( + repr(self) + ) + ) + @classmethod def style_state( cls, has_studio_override, is_invalid, is_overriden, is_modified @@ -564,7 +653,7 @@ class InputObject(SettingObject): self._is_modified = False value = NOT_SET - if self._as_widget: + if self.as_widget: value = parent_values elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) @@ -589,7 +678,12 @@ class InputObject(SettingObject): self.default_value = value self._has_studio_override = False self._had_studio_override = False - self.set_value(value) + try: + self.set_value(value) + except InvalidValueType as exc: + self.default_value = NOT_SET + self.defaults_not_set = True + self.log.warning(exc.msg) def update_studio_values(self, parent_values): self._state = None @@ -605,12 +699,17 @@ class InputObject(SettingObject): 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 + + try: + self.set_value(value) + except InvalidValueType as exc: + self.studio_value = NOT_SET + self.log.warning(exc.msg) def apply_overrides(self, parent_values): self._is_modified = False @@ -637,7 +736,11 @@ class InputObject(SettingObject): self._was_overriden = True value = override_value - self.set_value(value) + try: + self.set_value(value) + except InvalidValueType as exc: + self.override_value = NOT_SET + self.log.warning(exc.msg) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -681,6 +784,36 @@ class InputObject(SettingObject): def hierarchical_style_update(self): self.update_style() + def _style_state(self): + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self.is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + return state + + def update_style(self): + state = self._style_state() + if self._state == state: + return + + self._state = state + + self.input_field.setProperty("input-state", state) + self.input_field.style().polish(self.input_field) + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + def remove_overrides(self): self._is_overriden = False self._is_modified = False @@ -718,6 +851,7 @@ class InputObject(SettingObject): self._is_overriden = False return + self._state = None self._is_modified = False self._is_overriden = self._was_overriden @@ -753,6 +887,7 @@ class InputObject(SettingObject): class BooleanWidget(QtWidgets.QWidget, InputObject): default_input_value = True value_changed = QtCore.Signal(object) + valid_value_types = (bool, ) def __init__( self, input_data, parent, @@ -768,69 +903,45 @@ class BooleanWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: + if not self.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_TranslucentBackground) layout.addWidget(label_widget, 0) - self.label_widget = label_widget + self.label_widget = label_widget + + checkbox_height = self.style().pixelMetric( + QtWidgets.QStyle.PM_IndicatorHeight + ) + self.input_field = NiceCheckbox(height=checkbox_height, parent=self) - self.checkbox = QtWidgets.QCheckBox(self) spacer = QtWidgets.QWidget(self) - layout.addWidget(self.checkbox, 0) - layout.addWidget(spacer, 1) - spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.setFocusProxy(self.checkbox) + layout.addWidget(self.input_field, 0) + layout.addWidget(spacer, 1) - self.checkbox.stateChanged.connect(self._on_value_change) + self.setFocusProxy(self.input_field) + + self.input_field.stateChanged.connect(self._on_value_change) 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) - - def update_style(self): - 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, - self.is_invalid, - 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 + self.validate_value(value) + self.input_field.setChecked(value) def item_value(self): - return self.checkbox.isChecked() + return self.input_field.isChecked() class NumberWidget(QtWidgets.QWidget, InputObject): default_input_value = 0 value_changed = QtCore.Signal(object) input_modifiers = ("minimum", "maximum", "decimal") + valid_value_types = (int, float) def __init__( self, input_data, parent, @@ -861,46 +972,16 @@ class NumberWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) - self.label_widget = label_widget + self.label_widget = label_widget layout.addWidget(self.input_field, 1) self.input_field.valueChanged.connect(self._on_value_change) def set_value(self, value): + self.validate_value(value) self.input_field.setValue(value) - def update_style(self): - 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, - self.is_invalid, - self.is_overriden, - self.is_modified - ) - if self._state == state: - return - - if self._as_widget: - property_name = "input-state" - widget = self.input_field - else: - property_name = "state" - widget = self.label_widget - - widget.setProperty(property_name, state) - widget.style().polish(widget) - def item_value(self): return self.input_field.value() @@ -908,6 +989,7 @@ class NumberWidget(QtWidgets.QWidget, InputObject): class TextWidget(QtWidgets.QWidget, InputObject): default_input_value = "" value_changed = QtCore.Signal(object) + valid_value_types = (str, ) def __init__( self, input_data, parent, @@ -927,14 +1009,14 @@ class TextWidget(QtWidgets.QWidget, InputObject): layout.setSpacing(5) if self.multiline: - self.text_input = QtWidgets.QPlainTextEdit(self) + self.input_field = QtWidgets.QPlainTextEdit(self) else: - self.text_input = QtWidgets.QLineEdit(self) + self.input_field = QtWidgets.QLineEdit(self) if placeholder: - self.text_input.setPlaceholderText(placeholder) + self.input_field.setPlaceholderText(placeholder) - self.setFocusProxy(self.text_input) + self.setFocusProxy(self.input_field) layout_kwargs = {} if self.multiline: @@ -946,60 +1028,30 @@ class TextWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0, **layout_kwargs) - self.label_widget = label_widget + self.label_widget = label_widget - layout.addWidget(self.text_input, 1, **layout_kwargs) + layout.addWidget(self.input_field, 1, **layout_kwargs) - self.text_input.textChanged.connect(self._on_value_change) + self.input_field.textChanged.connect(self._on_value_change) def set_value(self, value): + self.validate_value(value) if self.multiline: - self.text_input.setPlainText(value) + self.input_field.setPlainText(value) else: - self.text_input.setText(value) - - def update_style(self): - 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, - 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) + self.input_field.setText(value) def item_value(self): if self.multiline: - return self.text_input.toPlainText() + return self.input_field.toPlainText() else: - return self.text_input.text() + return self.input_field.text() class PathInputWidget(QtWidgets.QWidget, InputObject): default_input_value = "" value_changed = QtCore.Signal(object) + valid_value_types = (str, ) def __init__( self, input_data, parent, @@ -1021,32 +1073,103 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget, 0) - self.label_widget = label_widget + self.label_widget = label_widget - self.path_input = PathInput(self) - self.setFocusProxy(self.path_input) - layout.addWidget(self.path_input, 1) + self.input_field = PathInput(self) + self.setFocusProxy(self.input_field) + layout.addWidget(self.input_field, 1) - self.path_input.textChanged.connect(self._on_value_change) + self.input_field.textChanged.connect(self._on_value_change) def set_value(self, value): - self.path_input.setText(value) + self.validate_value(value) + self.input_field.setText(value) def focusOutEvent(self, event): - self.path_input.clear_end_path() + self.input_field.clear_end_path() super(PathInput, self).focusOutEvent(event) + def item_value(self): + return self.input_field.text() + + +class EnumeratorWidget(QtWidgets.QWidget, InputObject): + default_input_value = True + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(EnumeratorWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + self.multiselection = input_data.get("multiselection") + self.enum_items = input_data["enum_items"] + if not self.enum_items: + raise ValueError("Attribute `enum_items` is not defined.") + + 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) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + if self.multiselection: + placeholder = input_data.get("placeholder") + self.input_field = MultiSelectionComboBox( + placeholder=placeholder, parent=self + ) + else: + self.input_field = ComboBox(self) + + first_value = NOT_SET + for enum_item in self.enum_items: + for value, label in enum_item.items(): + if first_value is NOT_SET: + first_value = value + self.input_field.addItem(label, value) + self._first_value = first_value + + if self.multiselection: + model = self.input_field.model() + for idx in range(self.input_field.count()): + model.item(idx).setCheckable(True) + + layout.addWidget(self.input_field, 0) + + self.setFocusProxy(self.input_field) + + self.input_field.value_changed.connect(self._on_value_change) + + @property + def default_input_value(self): + if self.multiselection: + return [] + return self._first_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 + self.input_field.set_value(value) + def update_style(self): - 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 - ) + if self.as_widget: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) else: state = self.style_state( self.has_studio_override, @@ -1058,18 +1181,15 @@ class PathInputWidget(QtWidgets.QWidget, InputObject): 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) + self._state = state + self.input_field.setProperty("input-state", state) + self.input_field.style().polish(self.input_field) + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) def item_value(self): - return self.path_input.text() + return self.input_field.value() class RawJsonInput(QtWidgets.QPlainTextEdit): @@ -1102,6 +1222,7 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): def set_value(self, value): if value is NOT_SET: value = "" + elif not isinstance(value, str): try: value = json.dumps(value, indent=4) @@ -1127,6 +1248,8 @@ class RawJsonInput(QtWidgets.QPlainTextEdit): class RawJsonWidget(QtWidgets.QWidget, InputObject): default_input_value = "{}" value_changed = QtCore.Signal(object) + valid_value_types = (str, dict, list, type(NOT_SET)) + allow_to_environment = True def __init__( self, input_data, parent, @@ -1142,81 +1265,66 @@ class RawJsonWidget(QtWidgets.QWidget, InputObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - self.text_input = RawJsonInput(self) - self.text_input.setSizePolicy( + self.input_field = RawJsonInput(self) + self.input_field.setSizePolicy( QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding ) - self.setFocusProxy(self.text_input) + self.setFocusProxy(self.input_field) - if not self._as_widget: + 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, alignment=QtCore.Qt.AlignTop) - self.label_widget = label_widget - layout.addWidget(self.text_input, 1, alignment=QtCore.Qt.AlignTop) + self.label_widget = label_widget - self.text_input.textChanged.connect(self._on_value_change) + layout.addWidget(self.input_field, 1, alignment=QtCore.Qt.AlignTop) + + self.input_field.textChanged.connect(self._on_value_change) def update_studio_values(self, parent_values): - self._is_invalid = self.text_input.has_invalid_value() + self._is_invalid = self.input_field.has_invalid_value() return super(RawJsonWidget, self).update_studio_values(parent_values) def set_value(self, value): - self.text_input.set_value(value) + self.validate_value(value) + self.input_field.set_value(value) def _on_value_change(self, *args, **kwargs): - self._is_invalid = self.text_input.has_invalid_value() + self._is_invalid = self.input_field.has_invalid_value() return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) - def update_style(self): - 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, - 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): if self.is_invalid: return NOT_SET - return self.text_input.json_value() + value = self.input_field.json_value() + if not self.is_environ: + return value + + output = {} + for key, value in value.items(): + output[key.upper()] = value + return output + + def config_value(self): + value = self.item_value() + value[METADATA_KEY] = { + "environments": { + self.env_group_key: list(value.keys()) + } + } + return {self.key: value} class ListItem(QtWidgets.QWidget, SettingObject): _btn_size = 20 value_changed = QtCore.Signal(object) def __init__( - self, object_type, input_modifiers, config_parent, parent, - is_strict=False + self, item_schema, config_parent, parent, is_strict=False ): super(ListItem, self).__init__(parent) @@ -1268,9 +1376,9 @@ class ListItem(QtWidgets.QWidget, SettingObject): layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) - ItemKlass = TypeToKlass.types[object_type] + ItemKlass = TypeToKlass.types[item_schema["type"]] self.value_input = ItemKlass( - input_modifiers, + item_schema, self, as_widget=True, label_widget=None @@ -1364,6 +1472,12 @@ class ListItem(QtWidgets.QWidget, SettingObject): return self.value_input.item_value() return NOT_SET + @property + def is_modified(self): + if self._is_empty: + return False + return self.value_input.is_modified + @property def child_has_studio_override(self): return self.value_input.child_has_studio_override @@ -1395,6 +1509,7 @@ class ListItem(QtWidgets.QWidget, SettingObject): class ListWidget(QtWidgets.QWidget, InputObject): default_input_value = [] value_changed = QtCore.Signal(object) + valid_value_types = (list, ) def __init__( self, input_data, parent, @@ -1407,8 +1522,21 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.initial_attributes(input_data, parent, as_widget) - self.object_type = input_data["object_type"] - self.input_modifiers = input_data.get("input_modifiers") or {} + object_type = input_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + self.item_schema = { + "type": object_type + } + # Backwards compatibility + input_modifiers = input_data.get("input_modifiers") or {} + if input_modifiers: + self.log.warning(( + "Used deprecated key `input_modifiers` to define item." + " Rather use `object_type` as dictionary with modifiers." + )) + self.item_schema.update(input_modifiers) self.input_fields = [] @@ -1446,6 +1574,8 @@ class ListWidget(QtWidgets.QWidget, InputObject): self.hierarchical_style_update() def set_value(self, value): + self.validate_value(value) + previous_inputs = tuple(self.input_fields) for item_value in value: self.add_row(value=item_value) @@ -1477,9 +1607,7 @@ 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, self.inputs_widget - ) + item_widget = ListItem(self.item_schema, self, self.inputs_widget) previous_field = None next_field = None @@ -1594,30 +1722,28 @@ class ListWidget(QtWidgets.QWidget, InputObject): input_field.hierarchical_style_update() self.update_style() + @property + def is_modified(self): + is_modified = super(ListWidget, self).is_modified + if is_modified: + return is_modified + + for input_field in self.input_fields: + if input_field.is_modified: + return True + return False + def update_style(self): - 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, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if not self.label_widget: + return + + state = self._style_state() if self._state == state: return - if self.label_widget: - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + self._state = state + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) def item_value(self): output = [] @@ -1631,6 +1757,7 @@ class ListWidget(QtWidgets.QWidget, InputObject): class ListStrictWidget(QtWidgets.QWidget, InputObject): value_changed = QtCore.Signal(object) _default_input_value = None + valid_value_types = (list, ) def __init__( self, input_data, parent, @@ -1678,11 +1805,8 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): children_item_mapping = [] for child_configuration in input_data["object_types"]: - object_type = child_configuration["type"] - item_widget = ListItem( - object_type, child_configuration, self, self.inputs_widget, - is_strict=True + child_configuration, self, self.inputs_widget, is_strict=True ) self.input_fields.append(item_widget) @@ -1750,6 +1874,8 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): return self._default_input_value def set_value(self, value): + self.validate_value(value) + if self._is_overriden: method_name = "apply_overrides" elif not self._has_studio_override: @@ -1774,30 +1900,17 @@ class ListStrictWidget(QtWidgets.QWidget, InputObject): self.update_style() def update_style(self): - 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, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + if not self.label_widget: + return + + state = self._style_state() if self._state == state: return - if self.label_widget: - self.label_widget.setProperty("state", state) - self.label_widget.style().polish(self.label_widget) + self._state = state + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) def item_value(self): output = [] @@ -1810,7 +1923,7 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): _btn_size = 20 value_changed = QtCore.Signal(object) - def __init__(self, object_type, input_modifiers, config_parent, parent): + def __init__(self, item_schema, config_parent, parent): super(ModifiableDictItem, self).__init__(parent) self._set_default_attributes() @@ -1830,13 +1943,13 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) - ItemKlass = TypeToKlass.types[object_type] + ItemKlass = TypeToKlass.types[item_schema["type"]] self.key_input = QtWidgets.QLineEdit(self) self.key_input.setObjectName("DictKey") self.value_input = ItemKlass( - input_modifiers, + item_schema, self, as_widget=True, label_widget=None @@ -1939,6 +2052,8 @@ class ModifiableDictItem(QtWidgets.QWidget, SettingObject): @property def is_modified(self): + if self._is_empty: + return False return self.is_value_modified() or self.is_key_modified() def hierarchical_style_update(self): @@ -1985,6 +2100,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): # TODO this is actually input field (do not care if is group or not) value_changed = QtCore.Signal(object) expand_in_grid = True + valid_value_types = (dict, ) def __init__( self, input_data, parent, @@ -2001,6 +2117,22 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.key = input_data["key"] + object_type = input_data["object_type"] + if isinstance(object_type, dict): + self.item_schema = object_type + else: + # Backwards compatibility + self.item_schema = { + "type": object_type + } + input_modifiers = input_data.get("input_modifiers") or {} + if input_modifiers: + self.log.warning(( + "Used deprecated key `input_modifiers` to define item." + " Rather use `object_type` as dictionary with modifiers." + )) + self.item_schema.update(input_modifiers) + if input_data.get("highlight_content", False): content_state = "hightlighted" bottom_margin = 5 @@ -2014,6 +2146,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): if as_widget: body_widget = None + self.label_widget = label_widget else: body_widget = ExpandingWidget(input_data["label"], self) main_layout.addWidget(body_widget) @@ -2052,15 +2185,14 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.object_type = input_data["object_type"] - self.input_modifiers = input_data.get("input_modifiers") or {} - self.add_row(is_empty=True) def count(self): return len(self.input_fields) def set_value(self, value): + self.validate_value(value) + previous_inputs = tuple(self.input_fields) for item_key, item_value in value.items(): self.add_row(key=item_key, value=item_value) @@ -2109,48 +2241,46 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): self.value_changed.emit(self) + @property + def is_modified(self): + is_modified = super(ModifiableDict, self).is_modified + if is_modified: + return is_modified + + for input_field in self.input_fields: + if input_field.is_modified: + 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 update_style(self): - 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, - self.is_invalid, - self.is_overriden, - self.is_modified - ) + state = self._style_state() + if self._state == state: return + self._state = state + + if self.label_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + if not self.body_widget: + return + if state: child_state = "child-{}".format(state) else: child_state = "" - 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) - self.label_widget.style().polish(self.label_widget) - - self._state = 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 + ) def all_item_values(self): output = {} @@ -2167,7 +2297,7 @@ class ModifiableDict(QtWidgets.QWidget, 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.content_widget + self.item_schema, self, self.content_widget ) if is_empty: item_widget.set_as_empty() @@ -2236,6 +2366,7 @@ class ModifiableDict(QtWidgets.QWidget, InputObject): class DictWidget(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) expand_in_grid = True + valid_value_types = (dict, type(NOT_SET)) def __init__( self, input_data, parent, @@ -2307,6 +2438,8 @@ class DictWidget(QtWidgets.QWidget, SettingObject): def _ui_as_widget(self, input_data): body = QtWidgets.QWidget(self) body.setObjectName("DictAsWidgetBody") + show_borders = str(int(input_data.get("show_borders", True))) + body.setProperty("show_borders", show_borders) content_layout = QtWidgets.QGridLayout(body) content_layout.setContentsMargins(5, 5, 5, 5) @@ -2333,7 +2466,13 @@ class DictWidget(QtWidgets.QWidget, SettingObject): 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) + if child_configuration["type"] != "boolean": + self.log.warning(( + "SCHEMA BUG: Dictionary item has set as checkbox" + " item invalid type \"{}\". Expected \"boolean\"." + ).format(child_configuration["type"])) + else: + return self._add_checkbox_child(child_configuration) label_widget = None if not klass.expand_in_grid: @@ -2360,7 +2499,7 @@ class DictWidget(QtWidgets.QWidget, SettingObject): ) item.value_changed.connect(self._on_value_change) - self.body_widget.add_widget_after_label(item) + self.body_widget.add_widget_before_label(item) self.checkbox_widget = item self.input_fields.append(item) return item @@ -2384,13 +2523,22 @@ class DictWidget(QtWidgets.QWidget, SettingObject): self._has_studio_override = True def discard_changes(self): - self._is_overriden = self._was_overriden self._is_modified = False + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override for input_field in self.input_fields: input_field.discard_changes() self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + 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._state = None + self._is_overriden = self._was_overriden def set_as_overriden(self): if self.is_overriden: @@ -2414,6 +2562,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject): elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) + try: + self.validate_value(value) + except InvalidValueType as exc: + value = NOT_SET + self.log.warning(exc.msg) + for item in self.input_fields: item.update_default_values(value) @@ -2434,6 +2588,14 @@ class DictWidget(QtWidgets.QWidget, SettingObject): self._had_studio_override = bool(self._has_studio_override) + try: + self.validate_value(value) + except InvalidValueType as exc: + value = NOT_SET + self._has_studio_override = False + self._had_studio_override = False + self.log.warning(exc.msg) + for item in self.input_fields: item.update_studio_values(value) @@ -2442,7 +2604,9 @@ class DictWidget(QtWidgets.QWidget, SettingObject): self._state = None self._child_state = None - if not self.as_widget: + if self.as_widget: + override_values = parent_values + else: metadata = {} groups = tuple() override_values = NOT_SET @@ -2453,6 +2617,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject): self._is_overriden = self.key in groups + try: + self.validate_value(override_values) + except InvalidValueType as exc: + override_values = NOT_SET + self.log.warning(exc.msg) + for item in self.input_fields: item.apply_overrides(override_values) @@ -2576,6 +2746,33 @@ class DictWidget(QtWidgets.QWidget, SettingObject): output.update(input_field.config_value()) return output + def _override_values(self, project_overrides): + values = {} + groups = [] + for input_field in self.input_fields: + if project_overrides: + value, is_group = input_field.overrides() + else: + value, is_group = input_field.studio_overrides() + if value is NOT_SET: + continue + + if METADATA_KEY in value and METADATA_KEY in values: + new_metadata = value.pop(METADATA_KEY) + values[METADATA_KEY] = self.merge_metadata( + values[METADATA_KEY], new_metadata + ) + + values.update(value) + if is_group: + groups.extend(value.keys()) + + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return {self.key: values}, self.is_group + def studio_overrides(self): if ( not (self.as_widget or self.any_parent_as_widget) @@ -2583,34 +2780,12 @@ class DictWidget(QtWidgets.QWidget, SettingObject): 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.studio_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 + return self._override_values(False) 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 + return self._override_values(True) class DictInvisible(QtWidgets.QWidget, SettingObject): @@ -2618,6 +2793,7 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): value_changed = QtCore.Signal(object) allow_actions = False expand_in_grid = True + valid_value_types = (dict, type(NOT_SET)) def __init__( self, input_data, parent, @@ -2764,11 +2940,20 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): def discard_changes(self): self._is_modified = False self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override for input_field in self.input_fields: input_field.discard_changes() self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + 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._state = None + self._is_overriden = self._was_overriden def set_as_overriden(self): if self.is_overriden: @@ -2783,11 +2968,17 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): def update_default_values(self, parent_values): value = NOT_SET - if self._as_widget: + if self.as_widget: value = parent_values elif parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) + try: + self.validate_value(value) + except InvalidValueType as exc: + value = NOT_SET + self.log.warning(exc.msg) + for item in self.input_fields: item.update_default_values(value) @@ -2796,6 +2987,12 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): if parent_values is not NOT_SET: value = parent_values.get(self.key, NOT_SET) + try: + self.validate_value(value) + except InvalidValueType as exc: + value = NOT_SET + self.log.warning(exc.msg) + for item in self.input_fields: item.update_studio_values(value) @@ -2814,6 +3011,12 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): self._is_overriden = self.key in groups + try: + self.validate_value(override_values) + except InvalidValueType as exc: + override_values = NOT_SET + self.log.warning(exc.msg) + for item in self.input_fields: item.apply_overrides(override_values) @@ -2825,6 +3028,33 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): ) self._was_overriden = bool(self._is_overriden) + def _override_values(self, project_overrides): + values = {} + groups = [] + for input_field in self.input_fields: + if project_overrides: + value, is_group = input_field.overrides() + else: + value, is_group = input_field.studio_overrides() + if value is NOT_SET: + continue + + if METADATA_KEY in value and METADATA_KEY in values: + new_metadata = value.pop(METADATA_KEY) + values[METADATA_KEY] = self.merge_metadata( + values[METADATA_KEY], new_metadata + ) + + values.update(value) + if is_group: + groups.extend(value.keys()) + + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return {self.key: values}, self.is_group + def studio_overrides(self): if ( not (self.as_widget or self.any_parent_as_widget) @@ -2832,34 +3062,12 @@ class DictInvisible(QtWidgets.QWidget, SettingObject): 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.studio_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 + return self._override_values(False) 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 + return self._override_values(True) class PathWidget(QtWidgets.QWidget, SettingObject): @@ -2890,20 +3098,20 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self.multiplatform = input_data.get("multiplatform", False) self.multipath = input_data.get("multipath", False) - self.input_fields = [] + self.input_field = None layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) - if not self._as_widget: + if not self.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_TranslucentBackground) layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) - self.label_widget = label_widget + self.label_widget = label_widget self.content_widget = QtWidgets.QWidget(self) self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) @@ -2926,56 +3134,61 @@ class PathWidget(QtWidgets.QWidget, SettingObject): platform: value_type() for platform in self.platforms } - else: - return value_type() + return value_type() 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 + input_data, self, + as_widget=True, label_widget=self.label_widget ) self.setFocusProxy(path_input) self.content_layout.addWidget(path_input) - self.input_fields.append(path_input) + self.input_field = 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 + item_schema = { + "key": self.key, + "object_type": "path-input" + } input_widget = ListWidget( - input_data_for_list, self, label_widget=self.label_widget + item_schema, self, + as_widget=True, label_widget=self.label_widget ) self.setFocusProxy(input_widget) self.content_layout.addWidget(input_widget) - self.input_fields.append(input_widget) + self.input_field = 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) + item_schema = { + "type": "dict", + "show_borders": False, + "children": [] + } for platform_key in self.platforms: platform_label = self.platform_labels_mapping[platform_key] - label_widget = QtWidgets.QLabel(platform_label, proxy_widget) + child_item = { + "key": platform_key, + "label": platform_label + } if self.multipath: - input_data_for_list["key"] = platform_key - input_widget = ListWidget( - input_data_for_list, self, label_widget=label_widget - ) + child_item["type"] = "list" + child_item["object_type"] = "path-input" 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) + child_item["type"] = "path-input" - self.setFocusProxy(self.input_fields[0]) - self.content_layout.addWidget(proxy_widget) + item_schema["children"].append(child_item) + + input_widget = DictWidget( + item_schema, self, as_widget=True, label_widget=self.label_widget + ) + self.content_layout.addWidget(input_widget) + self.input_field = input_widget + input_widget.value_changed.connect(self._on_value_change) def update_default_values(self, parent_values): self._state = None @@ -2983,21 +3196,15 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._is_modified = False value = NOT_SET - if self._as_widget: + if self.as_widget: value = parent_values elif parent_values is not NOT_SET: - if not self.multiplatform: - value = parent_values - else: - value = parent_values.get(self.key, NOT_SET) + value = parent_values.get(self.key, NOT_SET) if value is NOT_SET: if self.develop_mode: - 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 + value = self.default_input_value if value is NOT_SET: raise NotImplementedError(( "{} Does not have implemented" @@ -3015,11 +3222,8 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._has_studio_override = False self._had_studio_override = False - if not self.multiplatform: - self.input_fields[0].update_default_values(value) - else: - for input_field in self.input_fields: - input_field.update_default_values(value) + # TODO handle invalid value type + self.input_field.update_default_values(value) def update_studio_values(self, parent_values): self._state = None @@ -3027,13 +3231,10 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self._is_modified = False value = NOT_SET - if self._as_widget: + if self.as_widget: value = parent_values elif parent_values is not NOT_SET: - if not self.multiplatform: - value = parent_values - else: - value = parent_values.get(self.key, NOT_SET) + value = parent_values.get(self.key, NOT_SET) self.studio_value = value if value is not NOT_SET: @@ -3042,13 +3243,9 @@ class PathWidget(QtWidgets.QWidget, SettingObject): else: self._has_studio_override = False self._had_studio_override = False - 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) + # TODO handle invalid value type + self.input_field.update_studio_values(value) def apply_overrides(self, parent_values): self._is_modified = False @@ -3059,19 +3256,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject): if self._as_widget: override_values = parent_values elif parent_values is not NOT_SET: - if not self.multiplatform: - override_values = parent_values - else: - override_values = parent_values.get(self.key, NOT_SET) + 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) - if not self.multiplatform: - self.input_fields[0].apply_overrides(parent_values) - else: - for input_field in self.input_fields: - input_field.apply_overrides(override_values) + # TODO handle invalid value type + self.input_field.update_studio_values(override_values) if not self._is_overriden: self._is_overriden = ( @@ -3084,12 +3275,13 @@ class PathWidget(QtWidgets.QWidget, SettingObject): def set_value(self, value): if not self.multiplatform: - self.input_fields[0].set_value(value) + return self.input_field.set_value(value) - else: - for input_field in self.input_fields: - _value = value[input_field.key] - input_field.set_value(_value) + for _input_field in self.input_field.input_fields: + _value = value.get(_input_field.key, NOT_SET) + if _value is NOT_SET: + continue + _input_field.set_value(_value) def _on_value_change(self, item=None): if self.ignore_value_changes: @@ -3132,7 +3324,7 @@ class PathWidget(QtWidgets.QWidget, SettingObject): self.style().polish(self) self._child_state = child_state - if not self._as_widget: + if self.label_widget: state = self.style_state( child_has_studio_override, child_invalid, @@ -3150,17 +3342,14 @@ class PathWidget(QtWidgets.QWidget, SettingObject): def remove_overrides(self): self._is_overriden = False self._is_modified = False - for input_field in self.input_fields: - input_field.remove_overrides() + self.input_field.remove_overrides() def reset_to_pype_default(self): - for input_field in self.input_fields: - input_field.reset_to_pype_default() + self.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() + self.input_field.set_studio_default() if self.is_group: self._has_studio_override = True @@ -3168,62 +3357,45 @@ class PathWidget(QtWidgets.QWidget, SettingObject): def discard_changes(self): self._is_modified = False self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override - for input_field in self.input_fields: - input_field.discard_changes() + self.input_field.discard_changes() self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + 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._state = None + self._is_overriden = self._was_overriden 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.has_studio_override - or input_field.child_has_studio_override - ): - return True - return False + return self.has_studio_override @property def child_modified(self): - for input_field in self.input_fields: - if input_field.child_modified: - return True - return False + return self.is_modified @property def child_overriden(self): - for input_field in self.input_fields: - if input_field.child_overriden: - return True - return False + return self.is_overriden @property def child_invalid(self): - for input_field in self.input_fields: - if input_field.child_invalid: - return True - return False + return self.input_field.child_invalid def hierarchical_style_update(self): - for input_field in self.input_fields: - input_field.hierarchical_style_update() + self.input_field.hierarchical_style_update() self.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 + return self.input_field.item_value() def studio_overrides(self): if ( @@ -3233,18 +3405,14 @@ class PathWidget(QtWidgets.QWidget, SettingObject): ): return NOT_SET, False - value = self.item_value() - if not self.multiplatform: - value = {self.key: value} + value = {self.key: self.item_value()} return value, self.is_group 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} + value = {self.key: self.item_value()} return value, self.is_group @@ -3316,11 +3484,20 @@ class DictFormWidget(QtWidgets.QWidget, SettingObject): def discard_changes(self): self._is_modified = False self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override - 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 + if not self.is_overidable and self.as_widget: + 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._state = None + self._is_overriden = self._was_overriden def remove_overrides(self): self._is_overriden = False @@ -3494,6 +3671,7 @@ TypeToKlass.types["path-input"] = PathInputWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["list"] = ListWidget TypeToKlass.types["list-strict"] = ListStrictWidget +TypeToKlass.types["enum"] = EnumeratorWidget TypeToKlass.types["dict-modifiable"] = ModifiableDict # DEPRECATED - remove when removed from schemas TypeToKlass.types["dict-item"] = DictWidget diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py index f54989cfd7..569e7bfbb7 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/pype/tools/settings/settings/widgets/lib.py @@ -1,7 +1,8 @@ import os +import re import json import copy -from pype.settings.lib import OVERRIDEN_KEY +from pype.settings.lib import M_OVERRIDEN_KEY, M_ENVIRONMENT_KEY from queue import Queue @@ -11,10 +12,50 @@ class TypeToKlass: NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})() -METADATA_KEY = type("METADATA_KEY", (), {}) +METADATA_KEY = type("METADATA_KEY", (), {})() OVERRIDE_VERSION = 1 CHILD_OFFSET = 15 +key_pattern = re.compile(r"(\{.*?[^{0]*\})") + + +def convert_gui_data_with_metadata(data, ignored_keys=None): + if not data or not isinstance(data, dict): + return data + + if ignored_keys is None: + ignored_keys = tuple() + + output = {} + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key in ignored_keys or key == "groups": + continue + + if key == "environments": + output[M_ENVIRONMENT_KEY] = value + else: + raise KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_with_metadata(value, ignored_keys) + return output + + +def convert_data_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if M_ENVIRONMENT_KEY in data: + data.pop(M_ENVIRONMENT_KEY) + + for key, value in data.items(): + output[key] = convert_data_to_gui_data(value, False) + + return output + def convert_gui_data_to_overrides(data, first=True): if not data or not isinstance(data, dict): @@ -23,14 +64,15 @@ def convert_gui_data_to_overrides(data, first=True): output = {} if first: output["__override_version__"] = OVERRIDE_VERSION + data = convert_gui_data_with_metadata(data) if METADATA_KEY in data: metadata = data.pop(METADATA_KEY) for key, value in metadata.items(): if key == "groups": - output[OVERRIDEN_KEY] = value + output[M_OVERRIDEN_KEY] = value else: - KeyError("Unknown metadata key \"{}\"".format(key)) + raise KeyError("Unknown metadata key \"{}\"".format(key)) for key, value in data.items(): output[key] = convert_gui_data_to_overrides(value, False) @@ -41,9 +83,12 @@ def convert_overrides_to_gui_data(data, first=True): if not data or not isinstance(data, dict): return data + if first: + data = convert_data_to_gui_data(data) + output = {} - if OVERRIDEN_KEY in data: - groups = data.pop(OVERRIDEN_KEY) + if M_OVERRIDEN_KEY in data: + groups = data.pop(M_OVERRIDEN_KEY) if METADATA_KEY not in output: output[METADATA_KEY] = {} output[METADATA_KEY]["groups"] = groups @@ -54,7 +99,111 @@ def convert_overrides_to_gui_data(data, first=True): return output -def _fill_inner_schemas(schema_data, schema_collection): +def _fill_schema_template_data( + template, template_data, required_keys=None, missing_keys=None +): + first = False + if required_keys is None: + first = True + required_keys = set() + missing_keys = set() + + _template = [] + default_values = {} + for item in template: + if isinstance(item, dict) and "__default_values__" in item: + default_values = item["__default_values__"] + else: + _template.append(item) + template = _template + + for key, value in default_values.items(): + if key not in template_data: + template_data[key] = value + + if not template: + output = template + + elif isinstance(template, list): + output = [] + for item in template: + output.append(_fill_schema_template_data( + item, template_data, required_keys, missing_keys + )) + + elif isinstance(template, dict): + output = {} + for key, value in template.items(): + output[key] = _fill_schema_template_data( + value, template_data, required_keys, missing_keys + ) + + elif isinstance(template, str): + # TODO find much better way how to handle filling template data + for replacement_string in key_pattern.findall(template): + key = str(replacement_string[1:-1]) + required_keys.add(key) + if key not in template_data: + missing_keys.add(key) + continue + + value = template_data[key] + if replacement_string == template: + # Replace the value with value from templates data + # - with this is possible to set value with different type + template = value + else: + # Only replace the key in string + template = template.replace(replacement_string, value) + output = template + + else: + output = template + + if first and missing_keys: + raise SchemaTemplateMissingKeys(missing_keys, required_keys) + + return output + + +def _fill_schema_template(child_data, schema_collection, schema_templates): + template_name = child_data["name"] + template = schema_templates.get(template_name) + if template is None: + if template_name in schema_collection: + raise KeyError(( + "Schema \"{}\" is used as `schema_template`" + ).format(template_name)) + raise KeyError("Schema template \"{}\" was not found".format( + template_name + )) + + template_data = child_data.get("template_data") or {} + try: + filled_child = _fill_schema_template_data( + template, template_data + ) + + except SchemaTemplateMissingKeys as exc: + raise SchemaTemplateMissingKeys( + exc.missing_keys, exc.required_keys, template_name + ) + + output = [] + for item in filled_child: + filled_item = _fill_inner_schemas( + item, schema_collection, schema_templates + ) + if filled_item["type"] == "schema_template": + output.extend(_fill_schema_template( + filled_item, schema_collection, schema_templates + )) + else: + output.append(filled_item) + return output + + +def _fill_inner_schemas(schema_data, schema_collection, schema_templates): if schema_data["type"] == "schema": raise ValueError("First item in schema data can't be schema.") @@ -64,21 +213,62 @@ def _fill_inner_schemas(schema_data, schema_collection): new_children = [] for child in children: - if child["type"] != "schema": - new_child = _fill_inner_schemas(child, schema_collection) - new_children.append(new_child) + child_type = child["type"] + if child_type == "schema": + schema_name = child["name"] + if schema_name not in schema_collection: + if schema_name in schema_templates: + raise KeyError(( + "Schema template \"{}\" is used as `schema`" + ).format(schema_name)) + raise KeyError( + "Schema \"{}\" was not found".format(schema_name) + ) + + filled_child = _fill_inner_schemas( + schema_collection[schema_name], + schema_collection, + schema_templates + ) + + elif child_type == "schema_template": + for filled_child in _fill_schema_template( + child, schema_collection, schema_templates + ): + new_children.append(filled_child) continue - new_child = _fill_inner_schemas( - schema_collection[child["name"]], - schema_collection - ) - new_children.append(new_child) + else: + filled_child = _fill_inner_schemas( + child, schema_collection, schema_templates + ) + + new_children.append(filled_child) schema_data["children"] = new_children return schema_data +class SchemaTemplateMissingKeys(Exception): + def __init__(self, missing_keys, required_keys, template_name=None): + self.missing_keys = missing_keys + self.required_keys = required_keys + if template_name: + msg = f"Schema template \"{template_name}\" require more keys.\n" + else: + msg = "" + msg += "Required keys: {}\nMissing keys: {}".format( + self.join_keys(required_keys), + self.join_keys(missing_keys) + ) + super(SchemaTemplateMissingKeys, self).__init__(msg) + + def join_keys(self, keys): + return ", ".join([ + f"\"{key}\"" for key in keys + ]) + + class SchemaMissingFileInfo(Exception): def __init__(self, invalid): full_path_keys = [] @@ -120,6 +310,21 @@ class SchemaDuplicatedKeys(Exception): super(SchemaDuplicatedKeys, self).__init__(msg) +class SchemaDuplicatedEnvGroupKeys(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 environment group keys. {}" + ).format(" || ".join(items)) + super(SchemaDuplicatedEnvGroupKeys, self).__init__(msg) + + def file_keys_from_schema(schema_data): output = [] item_type = schema_data["type"] @@ -277,10 +482,50 @@ def validate_keys_are_unique(schema_data, keys=None): raise SchemaDuplicatedKeys(invalid) +def validate_environment_groups_uniquenes( + schema_data, env_groups=None, keys=None +): + is_first = False + if env_groups is None: + is_first = True + env_groups = {} + keys = [] + + my_keys = copy.deepcopy(keys) + key = schema_data.get("key") + if key: + my_keys.append(key) + + env_group_key = schema_data.get("env_group_key") + if env_group_key: + if env_group_key not in env_groups: + env_groups[env_group_key] = [] + env_groups[env_group_key].append("/".join(my_keys)) + + children = schema_data.get("children") + if not children: + return + + for child in children: + validate_environment_groups_uniquenes( + child, env_groups, copy.deepcopy(my_keys) + ) + + if is_first: + invalid = {} + for env_group_key, key_paths in env_groups.items(): + if len(key_paths) > 1: + invalid[env_group_key] = key_paths + + if invalid: + raise SchemaDuplicatedEnvGroupKeys(invalid) + + 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) + validate_environment_groups_uniquenes(schema_data) def gui_schema(subfolder, main_schema_name): @@ -292,19 +537,30 @@ def gui_schema(subfolder, main_schema_name): ) loaded_schemas = {} - for filename in os.listdir(dirpath): - basename, ext = os.path.splitext(filename) - if ext != ".json": - continue + loaded_schema_templates = {} + for root, _, filenames in os.walk(dirpath): + for filename in filenames: + 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 + filepath = os.path.join(root, filename) + with open(filepath, "r") as json_stream: + try: + schema_data = json.load(json_stream) + except Exception as exc: + raise Exception(( + f"Unable to parse JSON file {filepath}\n{exc}" + )) from exc + if isinstance(schema_data, list): + loaded_schema_templates[basename] = schema_data + else: + loaded_schemas[basename] = schema_data main_schema = _fill_inner_schemas( loaded_schemas[main_schema_name], - loaded_schemas + loaded_schemas, + loaded_schema_templates ) validate_schema(main_schema) return main_schema diff --git a/pype/tools/settings/settings/widgets/multiselection_combobox.py b/pype/tools/settings/settings/widgets/multiselection_combobox.py new file mode 100644 index 0000000000..9a99561ea8 --- /dev/null +++ b/pype/tools/settings/settings/widgets/multiselection_combobox.py @@ -0,0 +1,317 @@ +from Qt import QtCore, QtGui, QtWidgets + + +class ComboItemDelegate(QtWidgets.QStyledItemDelegate): + """ + Helper styled delegate (mostly based on existing private Qt's + delegate used by the QtWidgets.QComboBox). Used to style the popup like a + list view (e.g windows style). + """ + + def paint(self, painter, option, index): + option = QtWidgets.QStyleOptionViewItem(option) + option.showDecorationSelected = True + + # option.state &= ( + # ~QtWidgets.QStyle.State_HasFocus + # & ~QtWidgets.QStyle.State_MouseOver + # ) + super(ComboItemDelegate, self).paint(painter, option, index) + + +class MultiSelectionComboBox(QtWidgets.QComboBox): + value_changed = QtCore.Signal() + ignored_keys = { + QtCore.Qt.Key_Up, + QtCore.Qt.Key_Down, + QtCore.Qt.Key_PageDown, + QtCore.Qt.Key_PageUp, + QtCore.Qt.Key_Home, + QtCore.Qt.Key_End + } + + top_bottom_padding = 2 + left_right_padding = 3 + left_offset = 4 + top_bottom_margins = 2 + item_spacing = 5 + + item_bg_color = QtGui.QColor("#31424e") + + def __init__( + self, parent=None, placeholder="", separator=", ", **kwargs + ): + super(MultiSelectionComboBox, self).__init__(parent=parent, **kwargs) + self.setObjectName("MultiSelectionComboBox") + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + self._popup_is_shown = False + self._block_mouse_release_timer = QtCore.QTimer(self, singleShot=True) + self._initial_mouse_pos = None + self._separator = separator + self.placeholder_text = placeholder + self.delegate = ComboItemDelegate(self) + self.setItemDelegate(self.delegate) + + self.lines = {} + self.item_height = ( + self.fontMetrics().height() + + (2 * self.top_bottom_padding) + + (2 * self.top_bottom_margins) + ) + + def mousePressEvent(self, event): + """Reimplemented.""" + self._popup_is_shown = False + super(MultiSelectionComboBox, self).mousePressEvent(event) + if self._popup_is_shown: + self._initial_mouse_pos = self.mapToGlobal(event.pos()) + self._block_mouse_release_timer.start( + QtWidgets.QApplication.doubleClickInterval() + ) + + def showPopup(self): + """Reimplemented.""" + super(MultiSelectionComboBox, self).showPopup() + view = self.view() + view.installEventFilter(self) + view.viewport().installEventFilter(self) + self._popup_is_shown = True + + def hidePopup(self): + """Reimplemented.""" + self.view().removeEventFilter(self) + self.view().viewport().removeEventFilter(self) + self._popup_is_shown = False + self._initial_mouse_pos = None + super(MultiSelectionComboBox, self).hidePopup() + self.view().clearFocus() + + def _event_popup_shown(self, obj, event): + if not self._popup_is_shown: + return + + current_index = self.view().currentIndex() + model = self.model() + + if event.type() == QtCore.QEvent.MouseMove: + if ( + self.view().isVisible() + and self._initial_mouse_pos is not None + and self._block_mouse_release_timer.isActive() + ): + diff = obj.mapToGlobal(event.pos()) - self._initial_mouse_pos + if diff.manhattanLength() > 9: + self._block_mouse_release_timer.stop() + return + + index_flags = current_index.flags() + state = current_index.data(QtCore.Qt.CheckStateRole) + new_state = None + + if event.type() == QtCore.QEvent.MouseButtonRelease: + if ( + self._block_mouse_release_timer.isActive() + or not current_index.isValid() + or not self.view().isVisible() + or not self.view().rect().contains(event.pos()) + or not index_flags & QtCore.Qt.ItemIsSelectable + or not index_flags & QtCore.Qt.ItemIsEnabled + or not index_flags & QtCore.Qt.ItemIsUserCheckable + ): + return + + if state == QtCore.Qt.Unchecked: + new_state = QtCore.Qt.Checked + else: + new_state = QtCore.Qt.Unchecked + + elif event.type() == QtCore.QEvent.KeyPress: + # TODO: handle QtCore.Qt.Key_Enter, Key_Return? + if event.key() == QtCore.Qt.Key_Space: + # toogle the current items check state + if ( + index_flags & QtCore.Qt.ItemIsUserCheckable + and index_flags & QtCore.Qt.ItemIsTristate + ): + new_state = QtCore.Qt.CheckState((int(state) + 1) % 3) + + elif index_flags & QtCore.Qt.ItemIsUserCheckable: + if state != QtCore.Qt.Checked: + new_state = QtCore.Qt.Checked + else: + new_state = QtCore.Qt.Unchecked + + if new_state is not None: + model.setData(current_index, new_state, QtCore.Qt.CheckStateRole) + self.view().update(current_index) + self.update_size_hint() + self.value_changed.emit() + return True + + def eventFilter(self, obj, event): + """Reimplemented.""" + result = self._event_popup_shown(obj, event) + if result is not None: + return result + + return super(MultiSelectionComboBox, self).eventFilter(obj, event) + + def paintEvent(self, event): + """Reimplemented.""" + painter = QtWidgets.QStylePainter(self) + option = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(option) + painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option) + + # draw the icon and text + items = self.checked_items_text() + if not items: + option.currentText = self.placeholder_text + option.palette.setCurrentColorGroup(QtGui.QPalette.Disabled) + painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option) + return + + font_metricts = self.fontMetrics() + for line, items in self.lines.items(): + top_y = ( + option.rect.top() + + (line * self.item_height) + + self.top_bottom_margins + ) + left_x = option.rect.left() + self.left_offset + for item in items: + label_rect = font_metricts.boundingRect(item) + label_height = label_rect.height() + + label_rect.moveTop(top_y) + label_rect.moveLeft(left_x) + label_rect.setHeight(self.item_height) + + bg_rect = QtCore.QRectF(label_rect) + bg_rect.setWidth( + label_rect.width() + + (2 * self.left_right_padding) + ) + left_x = bg_rect.right() + self.item_spacing + + label_rect.moveLeft(label_rect.x() + self.left_right_padding) + + bg_rect.setHeight(label_height + (2 * self.top_bottom_padding)) + bg_rect.moveTop(bg_rect.top() + self.top_bottom_margins) + + path = QtGui.QPainterPath() + path.addRoundedRect(bg_rect, 5, 5) + + painter.fillPath(path, self.item_bg_color) + + painter.drawText( + label_rect, + QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, + item + ) + + def resizeEvent(self, *args, **kwargs): + super(MultiSelectionComboBox, self).resizeEvent(*args, **kwargs) + self.update_size_hint() + + def update_size_hint(self): + self.lines = {} + + items = self.checked_items_text() + if not items: + self.update() + return + + option = QtWidgets.QStyleOptionComboBox() + self.initStyleOption(option) + btn_rect = self.style().subControlRect( + QtWidgets.QStyle.CC_ComboBox, + option, + QtWidgets.QStyle.SC_ComboBoxArrow + ) + total_width = option.rect.width() - btn_rect.width() + font_metricts = self.fontMetrics() + + line = 0 + self.lines = {line: []} + + font_metricts = self.fontMetrics() + default_left_x = 0 + self.left_offset + left_x = int(default_left_x) + for item in items: + rect = font_metricts.boundingRect(item) + width = rect.width() + (2 * self.left_right_padding) + right_x = left_x + width + if right_x > total_width: + left_x = int(default_left_x) + if self.lines.get(line): + line += 1 + self.lines[line] = [item] + left_x += width + else: + self.lines[line] = [item] + line += 1 + else: + self.lines[line].append(item) + left_x = left_x + width + self.item_spacing + + self.update() + self.updateGeometry() + + def sizeHint(self): + value = super(MultiSelectionComboBox, self).sizeHint() + lines = len(self.lines) + if lines == 0: + lines = 1 + value.setHeight( + (lines * self.item_height) + + (2 * self.top_bottom_margins) + ) + return value + + def setItemCheckState(self, index, state): + self.setItemData(index, state, QtCore.Qt.CheckStateRole) + + def set_value(self, values): + for idx in range(self.count()): + value = self.itemData(idx, role=QtCore.Qt.UserRole) + if value in values: + check_state = QtCore.Qt.Checked + else: + check_state = QtCore.Qt.Unchecked + self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole) + self.update_size_hint() + + def value(self): + items = list() + for idx in range(self.count()): + state = self.itemData(idx, role=QtCore.Qt.CheckStateRole) + if state == QtCore.Qt.Checked: + items.append( + self.itemData(idx, role=QtCore.Qt.UserRole) + ) + return items + + def checked_items_text(self): + items = list() + for idx in range(self.count()): + state = self.itemData(idx, role=QtCore.Qt.CheckStateRole) + if state == QtCore.Qt.Checked: + items.append(self.itemText(idx)) + return items + + def wheelEvent(self, event): + event.ignore() + + def keyPressEvent(self, event): + if ( + event.key() == QtCore.Qt.Key_Down + and event.modifiers() & QtCore.Qt.AltModifier + ): + return self.showPopup() + + if event.key() in self.ignored_keys: + return event.ignore() + + return super(MultiSelectionComboBox, self).keyPressEvent(event) diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py index 2a1f5fe804..b64b1aa8ac 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -25,6 +25,28 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox): return output +class ComboBox(QtWidgets.QComboBox): + value_changed = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(ComboBox, self).__init__(*args, **kwargs) + + self.currentIndexChanged.connect(self._on_change) + + def _on_change(self, *args, **kwargs): + self.value_changed.emit() + + def set_value(self, value): + for idx in range(self.count()): + _value = self.itemData(idx, role=QtCore.Qt.UserRole) + if _value == value: + self.setCurrentIndex(idx) + break + + def value(self): + return self.itemData(self.currentIndex(), role=QtCore.Qt.UserRole) + + class PathInput(QtWidgets.QLineEdit): def clear_end_path(self): value = self.text().strip() @@ -71,33 +93,51 @@ class ExpandingWidget(QtWidgets.QWidget): top_part = ClickableWidget(parent=self) + side_line_widget = QtWidgets.QWidget(top_part) + side_line_widget.setObjectName("SideLineWidget") + button_size = QtCore.QSize(5, 5) - button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle = QtWidgets.QToolButton(parent=side_line_widget) 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 = QtWidgets.QLabel(label, parent=side_line_widget) label_widget.setObjectName("DictLabel") - side_line_widget = QtWidgets.QWidget(top_part) - side_line_widget.setObjectName("SideLineWidget") + before_label_widget = QtWidgets.QWidget(side_line_widget) + before_label_layout = QtWidgets.QVBoxLayout(before_label_widget) + before_label_layout.setContentsMargins(0, 0, 0, 0) + + after_label_widget = QtWidgets.QWidget(side_line_widget) + after_label_layout = QtWidgets.QVBoxLayout(after_label_widget) + after_label_layout.setContentsMargins(0, 0, 0, 0) + + spacer_widget = QtWidgets.QWidget(side_line_widget) + 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(before_label_widget) side_line_layout.addWidget(label_widget) + side_line_layout.addWidget(after_label_widget) + side_line_layout.addWidget(spacer_widget, 1) top_part_layout = QtWidgets.QHBoxLayout(top_part) top_part_layout.setContentsMargins(0, 0, 0, 0) top_part_layout.addWidget(side_line_widget) + before_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.top_part_ending = None - self.after_label_layout = None - self.end_of_layout = None + self.after_label_layout = after_label_layout + self.before_label_layout = before_label_layout self.side_line_widget = side_line_widget self.side_line_layout = side_line_layout @@ -146,45 +186,10 @@ class ExpandingWidget(QtWidgets.QWidget): 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 add_widget_before_label(self, widget): + self.before_label_layout.addWidget(widget) def resizeEvent(self, event): super(ExpandingWidget, self).resizeEvent(event) @@ -243,10 +248,11 @@ class GridLabelWidget(QtWidgets.QWidget): self.properties = {} layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) + layout.setContentsMargins(0, 2, 0, 0) layout.setSpacing(0) label_proxy = QtWidgets.QWidget(self) + label_proxy_layout = QtWidgets.QHBoxLayout(label_proxy) label_proxy_layout.setContentsMargins(0, 0, 0, 0) label_proxy_layout.setSpacing(0) @@ -265,6 +271,9 @@ class GridLabelWidget(QtWidgets.QWidget): layout.addWidget(label_proxy, 0) layout.addWidget(spacer_widget_v, 1) + label_proxy.setAttribute(QtCore.Qt.WA_TranslucentBackground) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + self.label_widget = label_widget def setProperty(self, name, value): @@ -279,3 +288,191 @@ class GridLabelWidget(QtWidgets.QWidget): if self.input_field: return self.input_field.show_actions_menu(event) return super(GridLabelWidget, self).mouseReleaseEvent(event) + + +class NiceCheckboxMoveWidget(QtWidgets.QFrame): + def __init__(self, height, border_width, parent): + super(NiceCheckboxMoveWidget, self).__init__(parent=parent) + + self.checkstate = False + + self.half_size = int(height / 2) + self.full_size = self.half_size * 2 + self.border_width = border_width + self.setFixedHeight(self.full_size) + self.setFixedWidth(self.full_size) + + self.setStyleSheet(( + "background: #444444;border-style: none;" + "border-radius: {};border-width:{}px;" + ).format(self.half_size, self.border_width)) + + def update_position(self): + parent_rect = self.parent().rect() + if self.checkstate is True: + pos_x = ( + parent_rect.x() + + parent_rect.width() + - self.full_size + - self.border_width + ) + else: + pos_x = parent_rect.x() + self.border_width + + pos_y = parent_rect.y() + int( + parent_rect.height() / 2 - self.half_size + ) + self.setGeometry(pos_x, pos_y, self.width(), self.height()) + + def state_offset(self): + diff_x = ( + self.parent().rect().width() + - self.full_size + - (2 * self.border_width) + ) + return QtCore.QPoint(diff_x, 0) + + def change_position(self, checkstate): + self.checkstate = checkstate + + self.update_position() + + def resizeEvent(self, event): + super().resizeEvent(event) + self.update_position() + + +class NiceCheckbox(QtWidgets.QFrame): + stateChanged = QtCore.Signal(int) + checked_bg_color = QtGui.QColor(69, 128, 86) + unchecked_bg_color = QtGui.QColor(170, 80, 80) + + def set_bg_color(self, color): + self._bg_color = color + self.setStyleSheet(self._stylesheet_template.format( + color.red(), color.green(), color.blue() + )) + + def bg_color(self): + return self._bg_color + + bgcolor = QtCore.Property(QtGui.QColor, bg_color, set_bg_color) + + def __init__(self, checked=True, height=30, *args, **kwargs): + super(NiceCheckbox, self).__init__(*args, **kwargs) + + self._checkstate = checked + if checked: + bg_color = self.checked_bg_color + else: + bg_color = self.unchecked_bg_color + + self.half_height = int(height / 2) + height = self.half_height * 2 + tenth_height = int(height / 10) + + self.setFixedHeight(height) + self.setFixedWidth((height - tenth_height) * 2) + + move_item_size = height - (2 * tenth_height) + + self.move_item = NiceCheckboxMoveWidget( + move_item_size, tenth_height, self + ) + self.move_item.change_position(self._checkstate) + + self._stylesheet_template = ( + "border-radius: {}px;" + "border-width: {}px;" + "background: #333333;" + "border-style: solid;" + "border-color: #555555;" + ).format(self.half_height, tenth_height) + self._stylesheet_template += "background: rgb({},{},{});" + + self.set_bg_color(bg_color) + + def resizeEvent(self, event): + super(NiceCheckbox, self).resizeEvent(event) + self.move_item.update_position() + + def show(self, *args, **kwargs): + super(NiceCheckbox, self).show(*args, **kwargs) + self.move_item.update_position() + + def checkState(self): + if self._checkstate: + return QtCore.Qt.Checked + else: + return QtCore.Qt.Unchecked + + def _on_checkstate_change(self): + self.stateChanged.emit(self.checkState()) + + move_start_value = self.move_item.pos() + offset = self.move_item.state_offset() + if self._checkstate is True: + move_end_value = move_start_value + offset + else: + move_end_value = move_start_value - offset + move_animation = QtCore.QPropertyAnimation( + self.move_item, b"pos", self + ) + move_animation.setDuration(150) + move_animation.setEasingCurve(QtCore.QEasingCurve.OutQuad) + move_animation.setStartValue(move_start_value) + move_animation.setEndValue(move_end_value) + + color_animation = QtCore.QPropertyAnimation( + self, b"bgcolor" + ) + color_animation.setDuration(150) + if self._checkstate is True: + color_animation.setStartValue(self.unchecked_bg_color) + color_animation.setEndValue(self.checked_bg_color) + else: + color_animation.setStartValue(self.checked_bg_color) + color_animation.setEndValue(self.unchecked_bg_color) + + anim_group = QtCore.QParallelAnimationGroup(self) + anim_group.addAnimation(move_animation) + anim_group.addAnimation(color_animation) + + def _finished(): + self.move_item.change_position(self._checkstate) + + anim_group.finished.connect(_finished) + anim_group.start() + + def isChecked(self): + return self._checkstate + + def setChecked(self, checked): + if checked == self._checkstate: + return + self._checkstate = checked + self._on_checkstate_change() + + def setCheckState(self, state=None): + if state is None: + checkstate = not self._checkstate + elif state == QtCore.Qt.Checked: + checkstate = True + elif state == QtCore.Qt.Unchecked: + checkstate = False + else: + return + + if checkstate == self._checkstate: + return + + self._checkstate = checkstate + + self._on_checkstate_change() + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.setCheckState() + event.accept() + return + return super(NiceCheckbox, self).mouseReleaseEvent(event)