From 628833be97308401e3279929a9866da03c6d8d9d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 14:48:37 +0200 Subject: [PATCH 001/101] flame: adding timewarp effect scraping --- openpype/hosts/flame/api/lib.py | 153 ++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index d59308ad6c..02481a1d2e 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1,14 +1,16 @@ import sys import os import re +import sys import json import pickle import clique import tempfile +import traceback import itertools import contextlib import xml.etree.cElementTree as cET -from copy import deepcopy +from copy import deepcopy, copy from xml.etree import ElementTree as ET from pprint import pformat from .constants import ( @@ -266,7 +268,7 @@ def get_current_sequence(selection): def rescan_hooks(): import flame try: - flame.execute_shortcut('Rescan Python Hooks') + flame.execute_shortcut("Rescan Python Hooks") except Exception: pass @@ -1082,21 +1084,21 @@ class MediaInfoFile(object): xml_data (ET.Element): clip data """ try: - for out_track in xml_data.iter('track'): - for out_feed in out_track.iter('feed'): + for out_track in xml_data.iter("track"): + for out_feed in out_track.iter("feed"): # start frame out_feed_nb_ticks_obj = out_feed.find( - 'startTimecode/nbTicks') + "startTimecode/nbTicks") self.start_frame = out_feed_nb_ticks_obj.text # fps out_feed_fps_obj = out_feed.find( - 'startTimecode/rate') + "startTimecode/rate") self.fps = out_feed_fps_obj.text # drop frame mode out_feed_drop_mode_obj = out_feed.find( - 'startTimecode/dropMode') + "startTimecode/dropMode") self.drop_mode = out_feed_drop_mode_obj.text break except Exception as msg: @@ -1118,8 +1120,143 @@ class MediaInfoFile(object): tree = cET.ElementTree(xml_element_data) tree.write( fpath, xml_declaration=True, - method='xml', encoding='UTF-8' + method="xml", encoding="UTF-8" ) except IOError as error: raise IOError( "Not able to write data to file: {}".format(error)) + + +class TimeEffectMetadata(object): + log = log + temp_setup_path = "/var/tmp/temp_timewarp_setup.timewarp_node" + _data = {} + _retime_modes = { + 0: "speed", + 1: "timewarp", + 2: "duration" + } + + def __init__(self, segment=None, logger=None): + if logger: + self.log = logger + if segment: + self._data = self._get_metadata(segment) + + def _get_metadata(self, segment): + effects = segment.effects or [] + for effect in effects: + if effect.type == "Timewarp": + effect.save_setup(self.temp_setup_path) + + self._data = self._get_attributes_from_xml() + os.remove(self.temp_setup_path) + + def _get_attributes_from_xml(self): + with open(self.temp_setup_path, "r") as tw_setup_file: + tw_setup_string = tw_setup_file.read() + tw_setup_file.close() + + tw_setup_xml = ET.fromstring(tw_setup_string) + tw_setup = self._dictify(tw_setup_xml) + # pprint(tw_setup) + try: + tw_setup_state = tw_setup["Setup"]["State"][0] + mode = int( + tw_setup_state["TW_RetimerMode"][0]["_text"] + ) + r_data = { + "type": self._retime_modes[mode], + "effectStart": int( + tw_setup["Setup"]["Base"][0]["Range"][0]["Start"]), + "effectEnd": int( + tw_setup["Setup"]["Base"][0]["Range"][0]["End"]) + } + + if mode == 0: # speed + r_data[self._retime_modes[mode]] = int( + tw_setup_state["TW_Speed"] + [0]["Channel"][0]["Value"][0]["_text"] + ) / 100 + elif mode == 1: # timewarp + print("timing") + r_data[self._retime_modes[mode]] = self._get_anim_keys( + tw_setup_state["TW_Timing"] + ) + elif mode == 2: # duration + r_data[self._retime_modes[mode]] = { + "start": { + "source": int( + tw_setup_state["TW_DurationTiming"][0]["Channel"] + [0]["KFrames"][0]["Key"][0]["Value"][0]["_text"] + ), + "timeline": int( + tw_setup_state["TW_DurationTiming"][0]["Channel"] + [0]["KFrames"][0]["Key"][0]["Frame"][0]["_text"] + ) + }, + "end": { + "source": int( + tw_setup_state["TW_DurationTiming"][0]["Channel"] + [0]["KFrames"][0]["Key"][1]["Value"][0]["_text"] + ), + "timeline": int( + tw_setup_state["TW_DurationTiming"][0]["Channel"] + [0]["KFrames"][0]["Key"][1]["Frame"][0]["_text"] + ) + } + } + except Exception: + lines = traceback.format_exception(*sys.exc_info()) + self.log.error("\n".join(lines)) + return + + return r_data + + def _get_anim_keys(self, setup_cat, index=None): + return_data = { + "extrapolation": ( + setup_cat[0]["Channel"][0]["Extrap"][0]["_text"] + ), + "animKeys": [] + } + for key in setup_cat[0]["Channel"][0]["KFrames"][0]["Key"]: + if index and int(key["Index"]) != index: + continue + key_data = { + "source": float(key["Value"][0]["_text"]), + "timeline": float(key["Frame"][0]["_text"]), + "index": int(key["Index"]), + "curveMode": key["CurveMode"][0]["_text"], + "curveOrder": key["CurveOrder"][0]["_text"] + } + if key.get("TangentMode"): + key_data["tangentMode"] = key["TangentMode"][0]["_text"] + + return_data["animKeys"].append(key_data) + + return return_data + + def _dictify(self, xml_, root=True): + """ Convert xml object to dictionary + + Args: + xml_ (xml.etree.ElementTree.Element): xml data + root (bool, optional): is root available. Defaults to True. + + Returns: + dict: dictionarized xml + """ + + if root: + return {xml_.tag: self._dictify(xml_, False)} + + d = copy(xml_.attrib) + if xml_.text: + d["_text"] = xml_.text + + for x in xml_.findall("./*"): + if x.tag not in d: + d[x.tag] = [] + d[x.tag].append(self._dictify(x, False)) + return d From 2998253832daf43f62fc901de6dd11eccb2708fd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 14:58:43 +0200 Subject: [PATCH 002/101] flame: adding property to return data --- openpype/hosts/flame/api/lib.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index 02481a1d2e..a02acd85a7 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1129,7 +1129,6 @@ class MediaInfoFile(object): class TimeEffectMetadata(object): log = log - temp_setup_path = "/var/tmp/temp_timewarp_setup.timewarp_node" _data = {} _retime_modes = { 0: "speed", @@ -1137,23 +1136,34 @@ class TimeEffectMetadata(object): 2: "duration" } - def __init__(self, segment=None, logger=None): + def __init__(self, segment, logger=None): if logger: self.log = logger - if segment: - self._data = self._get_metadata(segment) + + self._data = self._get_metadata(segment) + + @property + def data(self): + """ Returns timewarp effect data + + Returns: + dict: retime data + """ + return self._data def _get_metadata(self, segment): effects = segment.effects or [] for effect in effects: if effect.type == "Timewarp": - effect.save_setup(self.temp_setup_path) + with maintained_temp_file_path(".timewarp_node") as tmp_path: + self.log.info("Temp File: {}".format(tmp_path)) + effect.save_setup(tmp_path) + return self._get_attributes_from_xml(tmp_path) - self._data = self._get_attributes_from_xml() - os.remove(self.temp_setup_path) + return {} - def _get_attributes_from_xml(self): - with open(self.temp_setup_path, "r") as tw_setup_file: + def _get_attributes_from_xml(self, tmp_path): + with open(tmp_path, "r") as tw_setup_file: tw_setup_string = tw_setup_file.read() tw_setup_file.close() From 7f9948eaad87d144db2fc58c5083798ebf34482f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 15:09:06 +0200 Subject: [PATCH 003/101] flame: adding timewarp class to api --- openpype/hosts/flame/api/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 2c461e5f16..76c1c93379 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -30,7 +30,8 @@ from .lib import ( maintained_temp_file_path, get_clip_segment, get_batch_group_from_desktop, - MediaInfoFile + MediaInfoFile, + TimeEffectMetadata ) from .utils import ( setup, @@ -107,6 +108,7 @@ __all__ = [ "get_clip_segment", "get_batch_group_from_desktop", "MediaInfoFile", + "TimeEffectMetadata", # pipeline "install", From 42fa3dd2097cf7d1b9c9442b042600981be64bb9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 15:09:25 +0200 Subject: [PATCH 004/101] flame: implementing timewarpmetadata class --- openpype/hosts/flame/otio/flame_export.py | 25 +++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index 1e4ef866ed..a111176e29 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -275,7 +275,7 @@ def create_otio_reference(clip_data, fps=None): def create_otio_clip(clip_data): - from openpype.hosts.flame.api import MediaInfoFile + from openpype.hosts.flame.api import MediaInfoFile, TimeEffectMetadata segment = clip_data["PySegment"] @@ -284,14 +284,27 @@ def create_otio_clip(clip_data): media_timecode_start = media_info.start_frame media_fps = media_info.fps + # Timewarp metadata + tw_data = TimeEffectMetadata(segment, logger=log).data + log.debug("__ tw_data: {}".format(tw_data)) + # define first frame first_frame = media_timecode_start or utils.get_frame_from_filename( clip_data["fpath"]) or 0 _clip_source_in = int(clip_data["source_in"]) _clip_source_out = int(clip_data["source_out"]) + _clip_source_duration = clip_data["source_duration"] + _clip_record_in = clip_data["record_in"] + _clip_record_out = clip_data["record_out"] _clip_record_duration = int(clip_data["record_duration"]) + log.debug("_ first_frame: {}".format(first_frame)) + log.debug("_ _clip_source_in: {}".format(_clip_source_in)) + log.debug("_ _clip_source_out: {}".format(_clip_source_out)) + log.debug("_ _clip_record_in: {}".format(_clip_record_in)) + log.debug("_ _clip_record_out: {}".format(_clip_record_out)) + # first solve if the reverse timing speed = 1 if clip_data["source_in"] > clip_data["source_out"]: @@ -307,13 +320,17 @@ def create_otio_clip(clip_data): # secondly check if any change of speed if source_duration != _clip_record_duration: retime_speed = float(source_duration) / float(_clip_record_duration) - log.debug("_ retime_speed: {}".format(retime_speed)) + log.debug("_ calculated speed: {}".format(retime_speed)) speed *= retime_speed - log.debug("_ source_in: {}".format(source_in)) - log.debug("_ source_out: {}".format(source_out)) + # get speed from metadata if available + if tw_data.get("speed"): + speed = tw_data["speed"] + log.debug("_ metadata speed: {}".format(speed)) + log.debug("_ speed: {}".format(speed)) log.debug("_ source_duration: {}".format(source_duration)) + log.debug("_ _clip_source_duration: {}".format(_clip_source_duration)) log.debug("_ _clip_record_duration: {}".format(_clip_record_duration)) # create media reference From 009d7fc1fb765f18cadf1782bd66a5c3b95c38ee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 15:18:07 +0200 Subject: [PATCH 005/101] flame: speed should be float --- openpype/hosts/flame/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index a02acd85a7..a5ae3c4468 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1184,7 +1184,7 @@ class TimeEffectMetadata(object): } if mode == 0: # speed - r_data[self._retime_modes[mode]] = int( + r_data[self._retime_modes[mode]] = float( tw_setup_state["TW_Speed"] [0]["Channel"][0]["Value"][0]["_text"] ) / 100 From 8eb5c1ccb30c6fb7bfb6cddd2eb82d3697a652c1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 15:37:16 +0200 Subject: [PATCH 006/101] flame: more frame debug printing --- openpype/hosts/flame/otio/flame_export.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/flame/otio/flame_export.py b/openpype/hosts/flame/otio/flame_export.py index a111176e29..6d6b33d2a1 100644 --- a/openpype/hosts/flame/otio/flame_export.py +++ b/openpype/hosts/flame/otio/flame_export.py @@ -289,16 +289,20 @@ def create_otio_clip(clip_data): log.debug("__ tw_data: {}".format(tw_data)) # define first frame - first_frame = media_timecode_start or utils.get_frame_from_filename( - clip_data["fpath"]) or 0 + file_first_frame = utils.get_frame_from_filename( + clip_data["fpath"]) + if file_first_frame: + file_first_frame = int(file_first_frame) + + first_frame = media_timecode_start or file_first_frame or 0 _clip_source_in = int(clip_data["source_in"]) _clip_source_out = int(clip_data["source_out"]) - _clip_source_duration = clip_data["source_duration"] _clip_record_in = clip_data["record_in"] _clip_record_out = clip_data["record_out"] _clip_record_duration = int(clip_data["record_duration"]) + log.debug("_ file_first_frame: {}".format(file_first_frame)) log.debug("_ first_frame: {}".format(first_frame)) log.debug("_ _clip_source_in: {}".format(_clip_source_in)) log.debug("_ _clip_source_out: {}".format(_clip_source_out)) @@ -315,6 +319,15 @@ def create_otio_clip(clip_data): source_in = _clip_source_in - int(first_frame) source_out = _clip_source_out - int(first_frame) + log.debug("_ source_in: {}".format(source_in)) + log.debug("_ source_out: {}".format(source_out)) + + if file_first_frame: + log.debug("_ file_source_in: {}".format( + file_first_frame + source_in)) + log.debug("_ file_source_in: {}".format( + file_first_frame + source_out)) + source_duration = (source_out - source_in + 1) # secondly check if any change of speed @@ -330,7 +343,6 @@ def create_otio_clip(clip_data): log.debug("_ speed: {}".format(speed)) log.debug("_ source_duration: {}".format(source_duration)) - log.debug("_ _clip_source_duration: {}".format(_clip_source_duration)) log.debug("_ _clip_record_duration: {}".format(_clip_record_duration)) # create media reference From 7676827644c385991e35687b303032dd491e9361 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 12 Aug 2022 17:27:47 +0200 Subject: [PATCH 007/101] initial commit of settings dev --- website/docs/dev_settings.md | 890 +++++++++++++++++++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 891 insertions(+) create mode 100644 website/docs/dev_settings.md diff --git a/website/docs/dev_settings.md b/website/docs/dev_settings.md new file mode 100644 index 0000000000..483bd18535 --- /dev/null +++ b/website/docs/dev_settings.md @@ -0,0 +1,890 @@ +--- +id: dev_settings +title: Settings +sidebar_label: Settings +--- + +Settings gives ability to change how OpenPype behaves in certain situations. Settings are split into 3 categories **system settings**, **project anatomy** and **project settings**. Project anatomy and project settings are in grouped into single category but there is a technical difference (explained later). Only difference in system and project settings is that system settings can't be technically handled on a project level or their values must be available no matter in which project are values received. Settings have headless entities or settings UI. + +There is one more category **local settings** but they don't have ability to be changed or defined easily. Local settings can change how settings work per machine, can affect both system and project settings but they're hardcoded for predefined values at this moment. + +## Settings schemas +System and project settings are defined by settings schemas. Schema define structure of output value, what value types output will contain, how settings are stored and how it's UI input will look. + +## Settings values +Output of settings is a json serializable value. There are 3 possible types of value **default values**, **studio overrides** and **project overrides**. Default values must be always available for all settings schemas, their values are stored to code. Default values is what everyone who just installed OpenPype will use as default values. It is good practice to set example values but they should be relevant. + +Setting overrides is what makes settings powerful tool. Overrides contain only a part of settings with additional metadata which describe which parts of settings values that should be replaced from overrides values. Using overrides gives ability to save only specific values and use default values for rest. It is super useful in project settings which have up to 2 levels of overrides. In project settings are used **default values** as base on which are applied **studio overrides** and then **project overrides**. In practice it is possible to save only studio overrides which affect all projects. Changes in studio overrides are then propagated to all projects without project overrides. But values can be locked on project level so studio overrides are not used. + +## Settings storage +As was mentined default values are stored into repository files. Overrides are stored to Mongo database. The value in mongo contain only overrides with metadata so their content on it's own is useless and must be used with combination of default values. System settings and project settings are stored into special collection. Single document represents one set of overrides with OpenPype version for which is stored. Settings are versioned and are loaded in specific order - current OpenPype version overrides or first lower available. If there are any overrides with same or lower version then first higher version is used. If there are any overrides then no overrides are applied. + +Project anatomy is stored into project document thus is not versioned and it's values are always overriden. Any changes in anatomy schema may have drastic effect on production and OpenPype updates. + +## Settings schema items +As was mentioned schema items define output type of values, how they are stored and how they look in UI. +- schemas are (by default) defined by a json files +- OpenPype core system settings schemas are stored in `~/openpype/settings/entities/schemas/system_schema/` and project settings in `~/openpype/settings/entities/schemas/projects_schema/` + - both contain `schema_main.json` which are entry points +- OpenPype modules/addons can define their settings schemas using `BaseModuleSettingsDef` in that case some functionality may be slightly modified +- single schema item is represented by dictionary (object) in json which has `"type"` key. + - **type** is only common key which is required for all schema items +- each item may have "input modifiers" (other keys in dictionary) and they may be required or optional based on the type +- there are special keys across all items + - `"is_file"` - this key is used when defaults values are stored which define that this key is a filename where it's values are stored + - key is validated must be once in hierarchy else it won't be possible to store default values + - make sense to fill it only if it's value if `true` + - `"is_group"` - define that all values under a key in settings hierarchy will be overridden if any value is modified + - this key is not allowed for all inputs as they may not have technical ability to handle it + - key is validated can be only once in hierarchy and is automatically filled on last possible item if is not defined in schemas + - make sense to fill it only if it's value if `true` +- all entities can have set `"tooltip"` key with description which will be shown in UI on hover + +### Inner schema +Settings schemas are big json files which would became unmanageable if would be in single file. To be able to split them into multiple files to help organize them special types `schema` and `template` were added. Both types are relating to a different file by filename. If json file contains dictionary it is considered as `schema` if contains list it is considered as `template`. + +#### schema +Schema item is replaced by content of entered schema name. It is recommended that schema file is used only once in settings hierarchy. Templates are meant for reusing. +- schema must have `"name"` key which is name of schema that should be used + +```javascript +{ + "type": "schema", + "name": "my_schema_name" +} +``` + +#### template +Templates are almost the same as schema items but can contain one or more items which can be formatted with additional data or some keys can be skipped if needed. Templates are meant for reusing the same schemas with ability to modify content. + +- legacy name is `schema_template` (still usable) +- template must have `"name"` key which is name of template file that should be used +- to fill formatting keys use `"template_data"` +- all items in template, except `__default_values__`, will replace `template` item in original schema +- template may contain other templates + +```javascript +// Example template json file content +[ + { + // Define default values for formatting values + // - gives ability to set the value but have default value + "__default_values__": { + "multipath_executables": true + } + }, { + "type": "raw-json", + "label": "{host_label} Environments", + "key": "{host_name}_environments" + }, { + "type": "path", + "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": "template_examples", + "label": "Schema template examples", + "children": [ + { + "type": "template", + "name": "example_template", + "template_data": [ + { + "host_label": "Maya 2019", + "host_name": "maya_2019", + "multipath_executables": false + }, + { + "host_label": "Maya 2020", + "host_name": "maya_2020" + }, + { + "host_label": "Maya 2021", + "host_name": "maya_2021" + } + ] + } + ] +} +``` +```javascript +// The same schema defined without templates +{ + "type": "dict", + "key": "template_examples", + "label": "Schema template examples", + "children": [ + { + "type": "raw-json", + "label": "Maya 2019 Environments", + "key": "maya_2019_environments" + }, { + "type": "path", + "key": "maya_2019_executables", + "label": "Maya 2019 - Full paths to executables", + "multiplatform": false, + "multipath": true + }, { + "type": "raw-json", + "label": "Maya 2020 Environments", + "key": "maya_2020_environments" + }, { + "type": "path", + "key": "maya_2020_executables", + "label": "Maya 2020 - Full paths to executables", + "multiplatform": true, + "multipath": true + }, { + "type": "raw-json", + "label": "Maya 2021 Environments", + "key": "maya_2021_environments" + }, { + "type": "path", + "key": "maya_2021_executables", + "label": "Maya 2021 - Full paths to executables", + "multiplatform": true, + "multipath": true + } + ] +} +``` + +Template data can be used only to fill templates in values but not in keys. It is also 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). +```javascript +{ + ... + // Allowed + "key": "{to_fill}" + ... + // Not allowed + "{to_fill}": "value" + ... +} +``` + +Because formatting value can be only string it is possible to use formatting values which are replaced with different type. +```javascript +// Template data +{ + "template_data": { + "executable_multiplatform": { + "type": "schema", + "name": "my_multiplatform_schema" + } + } +} +// Template content +{ + ... + // Allowed - value is replaced with dictionary + "multiplatform": "{executable_multiplatform}" + ... + // Not allowed - there is no way how it could be replaced + "multiplatform": "{executable_multiplatform}_enhanced_string" + ... +} +``` + +#### dynamic_schema +Dynamic schema item marks a place in settings schema where schemas defined by `BaseModuleSettingsDef` can be placed. +- example: +``` +{ + "type": "dynamic_schema", + "name": "project_settings/global" +} +``` +- `BaseModuleSettingsDef` with implemented `get_settings_schemas` can return a dictionary where key define a dynamic schema name and value schemas that will be put there +- dynamic schemas work almost the same way as templates + - one item can be replaced by multiple items (or by 0 items) +- goal is to dynamically load settings of OpenPype modules without having their schemas or default values in core repository + - values of these schemas are saved using the `BaseModuleSettingsDef` methods +- we recommend to use `JsonFilesSettingsDef` which has full implementation of storing default values to json files + - requires only to implement method `get_settings_root_path` which should return path to root directory where settings schema can be found and default values will be saved + +### Basic Dictionary inputs +These inputs wraps another inputs into {key: value} relation + +#### dict +- this is dictionary type wrapping more inputs with keys defined in schema +- may be used as dynamic children (e.g. in `list` or `dict-modifiable`) + - in that case the only key modifier is `children` which is list of it's keys + - USAGE: e.g. List of dictionaries where each dictionary have same structure. +- if is not used as dynamic children then must have defined `"key"` under which are it's values stored +- may be with or without `"label"` (only for GUI) + - `"label"` must be set to be able mark item as group with `"is_group"` key set to True +- item with label can visually wrap it's children + - this option is enabled by default to turn off set `"use_label_wrap"` to `False` + - label wrap is by default collapsible + - that can be set with key `"collapsible"` to `True`/`False` + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add lighter background with `"highlight_content"` (Default: `False`) + - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color + - output is dictionary `{the "key": children values}` +``` +# Example +{ + "key": "applications", + "type": "dict", + "label": "Applications", + "collapsible": true, + "highlight_content": true, + "is_group": true, + "is_file": true, + "children": [ + ...ITEMS... + ] +} + +# Without label +{ + "type": "dict", + "key": "global", + "children": [ + ...ITEMS... + ] +} + +# When used as widget +{ + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, { + "key": "hosts", + "label": "Hosts", + "type": "list", + "object_type": "text" + } + ... + ] + } +} +``` + +#### dict-roots +- entity can be used only in Project settings +- keys of dictionary are based on current project roots +- they are not updated "live" it is required to save root changes and then + modify values on this entity + # TODO do live updates +``` +{ + "type": "dict-roots", + "key": "roots", + "label": "Roots", + "object_type": { + "type": "path", + "multiplatform": true, + "multipath": false + } +} +``` + +#### dict-conditional +- is similar to `dict` but has always available one enum entity + - the enum entity has single selection and it's value define other children entities +- each value of enumerator have defined children that will be used + - there is no way how to have shared entities across multiple enum items +- value from enumerator is also stored next to other values + - to define the key under which will be enum value stored use `enum_key` + - `enum_key` must match key regex and any enum item can't have children with same key + - `enum_label` is label of the entity for UI purposes +- enum items are define with `enum_children` + - it's a list where each item represents single item for the enum + - all items in `enum_children` must have at least `key` key which represents value stored under `enum_key` + - enum items can define `label` for UI purposes + - most important part is that item can define `children` key where are definitions of it's children (`children` value works the same way as in `dict`) +- to set default value for `enum_key` set it with `enum_default` +- entity must have defined `"label"` if is not used as widget +- is set as group if any parent is not group (can't have children as group) +- may be with or without `"label"` (only for GUI) + - `"label"` must be set to be able mark item as group with `"is_group"` key set to True +- item with label can visually wrap it's children + - this option is enabled by default to turn off set `"use_label_wrap"` to `False` + - label wrap is by default collapsible + - that can be set with key `"collapsible"` to `True`/`False` + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add lighter background with `"highlight_content"` (Default: `False`) + - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color +- for UI porposes was added `enum_is_horizontal` which will make combobox appear next to children inputs instead of on top of them (Default: `False`) + - this has extended ability of `enum_on_right` which will move combobox to right side next to children widgets (Default: `False`) +- output is dictionary `{the "key": children values}` +- using this type as template item for list type can be used to create infinite hierarchies + +``` +# Example +{ + "type": "dict-conditional", + "key": "my_key", + "label": "My Key", + "enum_key": "type", + "enum_label": "label", + "enum_children": [ + # Each item must be a dictionary with 'key' + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": "text" + } + ] + }, + { + # Separator does not have children as "separator" value is enough + "key": "separator", + "label": "Separator" + } + ] +} +``` + +How output of the schema could look like on save: +``` +{ + "type": "separator" +} + +{ + "type": "action", + "key": "action_1", + "label": "Action 1", + "command": "run command -arg" +} + +{ + "type": "menu", + "children": [ + "child_1", + "child_2" + ] +} +``` + +### Inputs for setting any kind of value (`Pure` inputs) +- all inputs must have defined `"key"` if are not used as dynamic item + - they can also have defined `"label"` + +#### boolean +- simple checkbox, nothing more to set +``` +{ + "type": "boolean", + "key": "my_boolean_key", + "label": "Do you want to use Pype?" +} +``` + +#### number +- number input, can be used for both integer and float + - key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`) + - key `"minimum"` as minimum allowed number to enter (Default: `-99999`) + - key `"maxium"` as maximum allowed number to enter (Default: `99999`) +- key `"steps"` will change single step value of UI inputs (using arrows and wheel scroll) +- for UI it is possible to show slider to enable this option set `show_slider` to `true` +``` +{ + "type": "number", + "key": "fps", + "label": "Frame rate (FPS)" + "decimal": 2, + "minimum": 1, + "maximum": 300000 +} +``` + +``` +{ + "type": "number", + "key": "ratio", + "label": "Ratio" + "decimal": 3, + "minimum": 0, + "maximum": 1, + "show_slider": true +} +``` + +#### text +- simple text input + - key `"multiline"` allows to enter multiple lines of text (Default: `False`) + - key `"placeholder"` allows to show text inside input when is empty (Default: `None`) + +``` +{ + "type": "text", + "key": "deadline_pool", + "label": "Deadline pool" +} +``` + +#### path-input +- Do not use this input in schema please (use `path` instead) +- this input is implemented to add additional features to text input +- this is meant to be used in proxy input `path` + +#### raw-json +- a little bit enhanced text input for raw json +- can store dictionary (`{}`) or list (`[]`) but not both + - by default stores dictionary to change it to list set `is_list` to `True` +- has validations of json format +- output can be stored as string + - this is to allow any keys in dictionary + - set key `store_as_string` to `true` + - code using that setting must expected that value is string and use json module to convert it to python types + +``` +{ + "type": "raw-json", + "key": "profiles", + "label": "Extract Review profiles", + "is_list": true +} +``` + +#### enum +- enumeration of values that are predefined in schema +- 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 +- it is possible to set default selected value/s with `default` attribute + - it is recommended to use this option only in single selection mode + - at the end this option is used only when defying default settings value or in dynamic items + +``` +{ + "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-handles": "Skip handle frames"} + ] +} +``` + +#### anatomy-templates-enum +- enumeration of all available anatomy template keys +- have only single selection mode +- it is possible to define default value `default` + - `"work"` is used if default value is not specified +- enum values are not updated on the fly it is required to save templates and + reset settings to recache values +``` +{ + "key": "host", + "label": "Host name", + "type": "anatomy-templates-enum", + "default": "publish" +} +``` + +#### hosts-enum +- enumeration of available hosts +- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`) +- it is possible to add empty value (represented with empty string) with setting `"use_empty_value"` to `True` (Default: `False`) +- it is possible to set `"custom_labels"` for host names where key `""` is empty value (Default: `{}`) +- to filter host names it is required to define `"hosts_filter"` which is list of host names that will be available + - do not pass empty string if `use_empty_value` is enabled + - ignoring host names would be more dangerous in some cases +``` +{ + "key": "host", + "label": "Host name", + "type": "hosts-enum", + "multiselection": false, + "use_empty_value": true, + "custom_labels": { + "": "N/A", + "nuke": "Nuke" + }, + "hosts_filter": [ + "nuke" + ] +} +``` + +#### apps-enum +- enumeration of available application and their variants from system settings + - applications without host name are excluded +- can be used only in project settings +- has only `multiselection` +- used only in project anatomy +``` +{ + "type": "apps-enum", + "key": "applications", + "label": "Applications" +} +``` + +#### tools-enum +- enumeration of available tools and their variants from system settings +- can be used only in project settings +- has only `multiselection` +- used only in project anatomy +``` +{ + "type": "tools-enum", + "key": "tools_env", + "label": "Tools" +} +``` + +#### task-types-enum +- enumeration of task types from current project +- enum values are not updated on the fly and modifications of task types on project require save and reset to be propagated to this enum +- has set `multiselection` to `True` but can be changed to `False` in schema + +#### deadline_url-enum +- deadline module specific enumerator using deadline system settings to fill it's values +- TODO: move this type to deadline module + +### Inputs for setting value using Pure inputs +- these inputs also have required `"key"` +- attribute `"label"` is required in few conditions + - when item is marked `as_group` or when `use_label_wrap` +- they use Pure inputs "as widgets" + +#### list +- output is list +- items can be added and removed +- items in list must be the same type +- to wrap item in collapsible widget with label on top set `use_label_wrap` to `True` + - when this is used `collapsible` and `collapsed` can be set (same as `dict` item does) +- 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`) + 3.) enhancement of 1.) there is also support of `template` type but be carefull about endless loop of templates + - goal of using `template` is to easily change same item definitions in multiple lists + +1.) with item modifiers +``` +{ + "type": "list", + "key": "exclude_ports", + "label": "Exclude ports", + "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" +} +``` + +3.) with template definition +``` +# Schema of list item where template is used +{ + "type": "list", + "key": "menu_items", + "label": "Menu Items", + "object_type": { + "type": "template", + "name": "template_object_example" + } +} + +# WARNING: +# In this example the template use itself inside which will work in `list` +# but may cause an issue in other entity types (e.g. `dict`). + +'template_object_example.json' : +[ + { + "type": "dict-conditional", + "use_label_wrap": true, + "collapsible": true, + "key": "menu_items", + "label": "Menu items", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + } + ] + }, { + "key": "menu", + "label": "Menu", + "children": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": { + "type": "template", + "name": "template_object_example" + } + } + ] + } + ] + } +] +``` + +#### 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"` +- required keys may be defined under `"required_keys"` + - required keys must be defined as a list (e.g. `["key_1"]`) and are moved to the top + - these keys can't be removed or edited (it is possible to edit label if item is collapsible) +- 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 collapsible + - that can be set with key `"collapsible"` as `True`/`False` (Default: `True`) + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + +1.) with item modifiers +``` +{ + "type": "dict-modifiable", + "object_type": { + "type": "number", + "minimum": 0, + "maximum": 300 + }, + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true +} +``` + +2.) without modifiers +``` +{ + "type": "dict-modifiable", + "object_type": "text", + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true +} +``` + +#### path +- input for paths, use `path-input` internally +- has 2 input modifiers `"multiplatform"` and `"multipath"` + - `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs result is dictionary + - `"multipath"` - it is possible to enter multiple paths + - if both are enabled result is dictionary with lists + +``` +{ + "type": "path", + "key": "ffmpeg_path", + "label": "FFmpeg path", + "multiplatform": true, + "multipath": true +} +``` + +#### list-strict +- input for strict number of items in list +- each child item can be different type with different possible modifiers +- it is possible to display them in horizontal or vertical layout + - key `"horizontal"` as `True`/`False` (Default: `True`) +- each child may have defined `"label"` which is shown next to input + - label does not reflect modifications or overrides (TODO) +- children item are defined under key `"object_types"` which is list of dictionaries + - key `"children"` is not used because is used for hierarchy validations in schema +- USAGE: For colors, transformations, etc. Custom number and different modifiers + give ability to define if color is HUE or RGB, 0-255, 0-1, 0-100 etc. + +``` +{ + "type": "list-strict", + "key": "color", + "label": "Color", + "object_types": [ + { + "label": "Red", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, { + "label": "Green", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, { + "label": "Blue", + "type": "number", + "minimum": 0, + "maximum": 255, + "decimal": 0 + }, { + "label": "Alpha", + "type": "number", + "minimum": 0, + "maximum": 1, + "decimal": 6 + } + ] +} +``` + +#### color +- preimplemented entity to store and load color values +- entity store and expect list of 4 integers in range 0-255 + - integers represents rgba [Red, Green, Blue, Alpha] + +``` +{ + "type": "color", + "key": "bg_color", + "label": "Background Color" +} +``` + +### Noninteractive items +Items used only for UI purposes. + +#### label +- add label with note or explanations +- it is possible to use html tags inside the label +- set `work_wrap` to `true`/`false` if you want to enable word wrapping in UI (default: `false`) + +``` +{ + "type": "label", + "label": "RED LABEL: Normal label" +} +``` + +#### separator +- legacy name is `splitter` (still usable) +- visual separator of items (more divider than separator) + +``` +{ + "type": "separator" +} +``` + +### Anatomy +Anatomy represents data stored on project document. + +#### anatomy +- entity works similarly to `dict` +- anatomy has always all keys overridden with overrides + - overrides are not applied as all anatomy data must be available from project document + - all children must be groups + +### Proxy wrappers +- should wraps multiple inputs only visually +- these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled +- can't be used as widget (first item in e.g. `list`, `dict-modifiable`, etc.) + +#### form +- wraps inputs into form look layout +- should be used only for Pure inputs + +``` +{ + "type": "dict-form", + "children": [ + { + "type": "text", + "key": "deadline_department", + "label": "Deadline apartment" + }, { + "type": "number", + "key": "deadline_priority", + "label": "Deadline priority" + }, { + ... + } + ] +} +``` + + +#### collapsible-wrap +- wraps inputs into collapsible widget + - looks like `dict` but does not hold `"key"` +- should be used only for Pure inputs + +``` +{ + "type": "collapsible-wrap", + "label": "Collapsible example" + "children": [ + { + "type": "text", + "key": "_example_input_collapsible", + "label": "Example input in collapsible wrapper" + }, { + ... + } + ] +} diff --git a/website/sidebars.js b/website/sidebars.js index 9d60a5811c..b7b44bbada 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -152,6 +152,7 @@ module.exports = { "dev_build", "dev_testing", "dev_contribute", + "dev_settings", { type: "category", label: "Hosts integrations", From c3b69e86d4a64d63865dbdb55a06b3a1f7a67c38 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 12 Aug 2022 17:52:33 +0200 Subject: [PATCH 008/101] small tweaks --- website/docs/dev_settings.md | 64 ++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/website/docs/dev_settings.md b/website/docs/dev_settings.md index 483bd18535..cb16ae76ca 100644 --- a/website/docs/dev_settings.md +++ b/website/docs/dev_settings.md @@ -214,7 +214,7 @@ These inputs wraps another inputs into {key: value} relation #### dict - this is dictionary type wrapping more inputs with keys defined in schema -- may be used as dynamic children (e.g. in `list` or `dict-modifiable`) +- may be used as dynamic children (e.g. in [list](#list) or [dict-modifiable](#dict-modifiable)) - in that case the only key modifier is `children` which is list of it's keys - USAGE: e.g. List of dictionaries where each dictionary have same structure. - if is not used as dynamic children then must have defined `"key"` under which are it's values stored @@ -600,7 +600,7 @@ How output of the schema could look like on save: - 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`) + 2.) item type name as string without modifiers (e.g. [text](#text)) 3.) enhancement of 1.) there is also support of `template` type but be carefull about endless loop of templates - goal of using `template` is to easily change same item definitions in multiple lists @@ -690,18 +690,31 @@ How output of the schema could look like on save: - 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"` - required keys may be defined under `"required_keys"` - required keys must be defined as a list (e.g. `["key_1"]`) and are moved to the top - these keys can't be removed or edited (it is possible to edit label if item is collapsible) -- 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`) +- type of items is defined with key `"object_type"` + - there are 2 possible ways how to set the object type (Examples below): + 1. just a type name as string without modifiers (e.g. `"text"`) + 2. full types with modifiers as dictionary(`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` - this input can be collapsible + - `"use_label_wrap"` must be set to `True` (Default behavior) - that can be set with key `"collapsible"` as `True`/`False` (Default: `True`) - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) -1.) with item modifiers +1. **Object type** without modifiers +``` +{ + "type": "dict-modifiable", + "object_type": "text", + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true +} +``` + +2. **Object type** with item modifiers ``` { "type": "dict-modifiable", @@ -717,22 +730,10 @@ How output of the schema could look like on save: } ``` -2.) without modifiers -``` -{ - "type": "dict-modifiable", - "object_type": "text", - "is_group": true, - "key": "templates_mapping", - "label": "Muster - Templates mapping", - "is_file": true -} -``` - #### path - input for paths, use `path-input` internally - has 2 input modifiers `"multiplatform"` and `"multipath"` - - `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs result is dictionary + - `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs (result is dictionary) - `"multipath"` - it is possible to enter multiple paths - if both are enabled result is dictionary with lists @@ -797,6 +798,8 @@ How output of the schema could look like on save: - preimplemented entity to store and load color values - entity store and expect list of 4 integers in range 0-255 - integers represents rgba [Red, Green, Blue, Alpha] +- has modifier `"use_alpha"` which can be `True`/`False` + - alpha is always `255` if set to `True` and alpha slider is not visible in UI ``` { @@ -806,6 +809,13 @@ How output of the schema could look like on save: } ``` +### Anatomy +Anatomy represents data stored on project document. Item cares about **Project Anatomy**. + +#### anatomy +- entity is just enhanced [dict](#dict) item +- anatomy has always all keys overridden with overrides + ### Noninteractive items Items used only for UI purposes. @@ -831,15 +841,6 @@ Items used only for UI purposes. } ``` -### Anatomy -Anatomy represents data stored on project document. - -#### anatomy -- entity works similarly to `dict` -- anatomy has always all keys overridden with overrides - - overrides are not applied as all anatomy data must be available from project document - - all children must be groups - ### Proxy wrappers - should wraps multiple inputs only visually - these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled @@ -888,3 +889,8 @@ Anatomy represents data stored on project document. } ] } +``` + + +## How to add new settings +Always start with modifying or adding new schema and don't worry about values. When you think schema is ready to use launch OpenPype settings in development mode using `poetry run python ./start.py settings --dev` or prepared script in `~/openpype/tools/run_settings(.sh|.ps1)`. Settings opened in development mode have checkbox `Modify defaults` available in bottom left corner. When checked default values are modified and saved on `Save`. This is recommended approach how default settings should be created instead of direct modification of files. From 1469c471d15c5556e4f0b2f5d06f1e07dcc74724 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 12 Aug 2022 18:23:12 +0200 Subject: [PATCH 009/101] added javascript notation --- website/docs/dev_settings.md | 74 ++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/website/docs/dev_settings.md b/website/docs/dev_settings.md index cb16ae76ca..e5917c7549 100644 --- a/website/docs/dev_settings.md +++ b/website/docs/dev_settings.md @@ -195,7 +195,7 @@ Because formatting value can be only string it is possible to use formatting val #### dynamic_schema Dynamic schema item marks a place in settings schema where schemas defined by `BaseModuleSettingsDef` can be placed. - example: -``` +```javascript { "type": "dynamic_schema", "name": "project_settings/global" @@ -228,8 +228,8 @@ These inputs wraps another inputs into {key: value} relation - it is possible to add lighter background with `"highlight_content"` (Default: `False`) - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color - output is dictionary `{the "key": children values}` -``` -# Example +```javascript +// Example { "key": "applications", "type": "dict", @@ -243,7 +243,7 @@ These inputs wraps another inputs into {key: value} relation ] } -# Without label +// Without label { "type": "dict", "key": "global", @@ -252,7 +252,7 @@ These inputs wraps another inputs into {key: value} relation ] } -# When used as widget +// When used as widget { "type": "list", "key": "profiles", @@ -283,7 +283,7 @@ These inputs wraps another inputs into {key: value} relation - they are not updated "live" it is required to save root changes and then modify values on this entity # TODO do live updates -``` +```javascript { "type": "dict-roots", "key": "roots", @@ -327,8 +327,8 @@ These inputs wraps another inputs into {key: value} relation - output is dictionary `{the "key": children values}` - using this type as template item for list type can be used to create infinite hierarchies -``` -# Example +```javascript +// Example { "type": "dict-conditional", "key": "my_key", @@ -336,7 +336,7 @@ These inputs wraps another inputs into {key: value} relation "enum_key": "type", "enum_label": "label", "enum_children": [ - # Each item must be a dictionary with 'key' + // Each item must be a dictionary with 'key' { "key": "action", "label": "Action", @@ -371,7 +371,7 @@ These inputs wraps another inputs into {key: value} relation ] }, { - # Separator does not have children as "separator" value is enough + // Separator does not have children as "separator" value is enough "key": "separator", "label": "Separator" } @@ -380,7 +380,7 @@ These inputs wraps another inputs into {key: value} relation ``` How output of the schema could look like on save: -``` +```javascript { "type": "separator" } @@ -407,7 +407,7 @@ How output of the schema could look like on save: #### boolean - simple checkbox, nothing more to set -``` +```javascript { "type": "boolean", "key": "my_boolean_key", @@ -422,7 +422,7 @@ How output of the schema could look like on save: - key `"maxium"` as maximum allowed number to enter (Default: `99999`) - key `"steps"` will change single step value of UI inputs (using arrows and wheel scroll) - for UI it is possible to show slider to enable this option set `show_slider` to `true` -``` +```javascript { "type": "number", "key": "fps", @@ -433,7 +433,7 @@ How output of the schema could look like on save: } ``` -``` +```javascript { "type": "number", "key": "ratio", @@ -450,7 +450,7 @@ How output of the schema could look like on save: - key `"multiline"` allows to enter multiple lines of text (Default: `False`) - key `"placeholder"` allows to show text inside input when is empty (Default: `None`) -``` +```javascript { "type": "text", "key": "deadline_pool", @@ -473,7 +473,7 @@ How output of the schema could look like on save: - set key `store_as_string` to `true` - code using that setting must expected that value is string and use json module to convert it to python types -``` +```javascript { "type": "raw-json", "key": "profiles", @@ -492,7 +492,7 @@ How output of the schema could look like on save: - it is recommended to use this option only in single selection mode - at the end this option is used only when defying default settings value or in dynamic items -``` +```javascript { "key": "tags", "label": "Tags", @@ -515,7 +515,7 @@ How output of the schema could look like on save: - `"work"` is used if default value is not specified - enum values are not updated on the fly it is required to save templates and reset settings to recache values -``` +```javascript { "key": "host", "label": "Host name", @@ -532,7 +532,7 @@ How output of the schema could look like on save: - to filter host names it is required to define `"hosts_filter"` which is list of host names that will be available - do not pass empty string if `use_empty_value` is enabled - ignoring host names would be more dangerous in some cases -``` +```javascript { "key": "host", "label": "Host name", @@ -555,7 +555,7 @@ How output of the schema could look like on save: - can be used only in project settings - has only `multiselection` - used only in project anatomy -``` +```javascript { "type": "apps-enum", "key": "applications", @@ -568,7 +568,7 @@ How output of the schema could look like on save: - can be used only in project settings - has only `multiselection` - used only in project anatomy -``` +```javascript { "type": "tools-enum", "key": "tools_env", @@ -605,7 +605,7 @@ How output of the schema could look like on save: - goal of using `template` is to easily change same item definitions in multiple lists 1.) with item modifiers -``` +```javascript { "type": "list", "key": "exclude_ports", @@ -619,7 +619,7 @@ How output of the schema could look like on save: ``` 2.) without modifiers -``` +```javascript { "type": "list", "key": "exclude_ports", @@ -629,8 +629,8 @@ How output of the schema could look like on save: ``` 3.) with template definition -``` -# Schema of list item where template is used +```javascript +// Schema of list item where template is used { "type": "list", "key": "menu_items", @@ -641,9 +641,9 @@ How output of the schema could look like on save: } } -# WARNING: -# In this example the template use itself inside which will work in `list` -# but may cause an issue in other entity types (e.g. `dict`). +// WARNING: +// In this example the template use itself inside which will work in `list` +// but may cause an issue in other entity types (e.g. `dict`). 'template_object_example.json' : [ @@ -703,7 +703,7 @@ How output of the schema could look like on save: - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) 1. **Object type** without modifiers -``` +```javascript { "type": "dict-modifiable", "object_type": "text", @@ -715,7 +715,7 @@ How output of the schema could look like on save: ``` 2. **Object type** with item modifiers -``` +```javascript { "type": "dict-modifiable", "object_type": { @@ -737,7 +737,7 @@ How output of the schema could look like on save: - `"multipath"` - it is possible to enter multiple paths - if both are enabled result is dictionary with lists -``` +```javascript { "type": "path", "key": "ffmpeg_path", @@ -759,7 +759,7 @@ How output of the schema could look like on save: - USAGE: For colors, transformations, etc. Custom number and different modifiers give ability to define if color is HUE or RGB, 0-255, 0-1, 0-100 etc. -``` +```javascript { "type": "list-strict", "key": "color", @@ -801,7 +801,7 @@ How output of the schema could look like on save: - has modifier `"use_alpha"` which can be `True`/`False` - alpha is always `255` if set to `True` and alpha slider is not visible in UI -``` +```javascript { "type": "color", "key": "bg_color", @@ -824,7 +824,7 @@ Items used only for UI purposes. - it is possible to use html tags inside the label - set `work_wrap` to `true`/`false` if you want to enable word wrapping in UI (default: `false`) -``` +```javascript { "type": "label", "label": "RED LABEL: Normal label" @@ -835,7 +835,7 @@ Items used only for UI purposes. - legacy name is `splitter` (still usable) - visual separator of items (more divider than separator) -``` +```javascript { "type": "separator" } @@ -850,7 +850,7 @@ Items used only for UI purposes. - wraps inputs into form look layout - should be used only for Pure inputs -``` +```javascript { "type": "dict-form", "children": [ @@ -875,7 +875,7 @@ Items used only for UI purposes. - looks like `dict` but does not hold `"key"` - should be used only for Pure inputs -``` +```javascript { "type": "collapsible-wrap", "label": "Collapsible example" From cbff5972a2b1c6aa7ebd9b19d519f122c955fe61 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 12 Aug 2022 18:23:24 +0200 Subject: [PATCH 010/101] added screenshot of settings UI in dev mode --- website/docs/assets/settings_dev.png | Bin 0 -> 15237 bytes website/docs/dev_settings.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 website/docs/assets/settings_dev.png diff --git a/website/docs/assets/settings_dev.png b/website/docs/assets/settings_dev.png new file mode 100644 index 0000000000000000000000000000000000000000..4d0359461e570163925bda6b3a2f5dd1c87c3e7b GIT binary patch literal 15237 zcmeHucT|&Un|BrgbC8of_j8x)zOLW(y9&`aER02W zAK488fkaKNUAYMY?VJIDe!BPTPrx@Oyc{Fo*N%{z#+N|lJu(#F!_QuZ=7u0pMZzBL z?Onj<--50=gn&R|EyAB2?a;S(K_J~DCRYrt@4HgR5s@w)*(&_xd~>>aQi|)%>+l`c z=MMVsdtIiKBDE|3(}yK%-5!wG`L9{~WDgrA@b$!YFXrz3nq@e3`m46Lv55Guwx3`A zRCAE|ZTFMsyIdZU2|o=2eIq%>QxcjTYNl#Bm(3e-&@mS$X`?6jH*X%X z-@GNjtcQyTxBeVowYuuE-D4HAdv~QIAe!u=`NC8ejxUQ|GR|H|L6XCVsiQ8<2rpiF zwq~7O64@yY1p%1k_ zp6Hl;sRS)2HL4+YfIy+yk1ltL>71IuIW>g(Xm%V>sa;8`-cMXvnK8CLXoTPM#&uVV zDM!%v>KKZQB0he^)M>IbOw8~crvKWrx$KH6i*#R|wHnA> z(|`^3xRov|2{^bVv+N7Tz&7QaLojW?N8|+a{t=bW5RVN_?~8@QL&L*|28P?E@UYw5 z)AE{ANsHOsf;|dxvYOgGOFs4qN$d>vIBAdA?;?{iDU(b`c5(zY53F5~^l3GMa?!0( zv0=Jb=ZsvtCFjkOLcO@7VAKa&n?;-zYIE+h)8r zlHeJAvu#`NLJO1}v4xUD#%;SyZmKAks$3T%SHJ|dJZ62`gyM4cUrgK1P3$Ccg*VL2 zS8c{{Dd;$NrYu#_TE4#{hfg2YV?Ex^P9(Y^=Ib{sbWh6Wa48VCw0ICG_wjlXy!`^a zG&xOfuw4~T&`#}(*@MRUkc;Farbmra14wqMF%*1~0rRxZJMTZ?k!sjp!N-tc=ZqW} zgs_#jsD#!q8LYp_H~@om*QBW@k0jAvT91Y&>aNmCB@39>BiAbn4U$X0j(^5^2Td0Y z>%ngL5J(@_ep6`x!)Z^gW=J&=vs>yUr&{(y>7FEnvP&QpFtTqU>Zh3T9t?X$ReWxF z`CEf!EBi?Wi{)L7%bVEICU?7lgA&Z^a!*Vo@OAIMa|y@$XT=BXa{|}L6tNzUr32&m zH4clN2Zk~0j}r=IFMy*1yD!i}w3>o_0#91>iP+901@j{0@mq8G#VA1@DG8zn7ib9d z%cgK^N;f4ZQvJF*y1GSVB0G_5QP(&Dv_=(ZU5eJN zEO-n7HY{4eY#+KS(uXQz6dH%s7%{_;yO%i!hnPyGD-wf4g9{hn^tVS59d60ldT-uB zbqsE}pMv(MjrBIJxP{!-8Lm&1VX;fPi0shSHymkK_=Jnr{GHV&sjdeKlDF)`?GsT% zOvV=JToHI}X?;~902Oo9Bq1V;lIr%#bUEr`orVZ^kl zK;zDiXn*tYZ)-&r!vz(blZZFnj_&|%8f2E2(4B4!W8WLZQ}BCyS=5pQY}D>I6Gpp= z#HF}{Ns}6lq1NGy9f^lhKOccv^#7u^Hc)DVx7;5k?VMzj<{owSf*0<^f^6utIZUaU zNgH-+<_ygPLj3^q9(FpR#E8pjjLRwF1>5~BIKp#?{4JNV3zTczNjHYBqI{q?p*56( zS{Twrpk1h7!ux7vY`}e&KH10^-%!?}f|b+mbMmbUU%k2;ReN+eCfL$_w>cdA=JR#f zKoH%_eGjLj$Rd^oop{zSwq|6=c&tm?GJKWVPu@5L85@6}C6hhCO<3JfkM1RY<0pnv z=7w(Gvqe+OOP%CF4={66h^x9O15Q5hGo>X;<)(Mw>!;7HI-Sq0g9#>#cyI}Vea*&3 zjNJrorfl>w_u%X&&N6zx%PT+WGpDa@>V75Hes7A#o~W5~UcB!3j_{q3AFW5d;Ddg7 zk#IWgzJPb2s+N(p+0^!=h>)nHRogEU8ALN6-k<)k_S)tCBxI&5c}@m>2&wJ|kNBgp zTOC!!h3o}=*mvD(=woG3Lh>1_!Z|O7b;iJKQDubm!2#T>TDl{zA01FUMl7kz&9#%6 z@0Gg&CvOpFm|IhdXf8YDO$NR|0}UIk+c14v(2jG+x(U^%)>usV=^0S+QZ+S;WRh{( zH?foY%vW}Ngo4&LKxN%)oXzP@&h}(ndHA2!WNdjkg!+Kcu^Cgb6`T(X%uEH$ju@ok zBWXI?pXuoPiP0*nrB3YuTuT%&cz{29z=GfMdfqB(uRin|N&Z!K5cB)<)#%ggv4}jc z_;Z{XRf$DW<*t+lvq156E7bSCP`A`6^?;WsOFkCy6X-#UPd%UzpruQ<)yn}N67lam z*Bu?e2^*VRQ73OKljzW?T#jH)aM^oY26sQ=BIH-Lz8dA!k+ zCq~HUoP$@66;5wiwdE*fffFJb8`~DY~1c=AA!NE+tXn6kCF|;NK)Q}o@ z2yp9D_m39t1pOKGHE;{=1^2XHp^M-LQU|TPUP32dtIBu5UULX>}Vl5vH(cn`qbFNPDy*-#G*o+?t4x zV-LKe7zV6y3v}dK-sj9h*_FLE^X`# zc|%7ocOqQ7Z2R|fOaz-ES_1WS=dGF?y$CH@6r)pRFX6T;7AZSzlDc^9jwz?t6NQRmx9=i0| z99oogo8IU-Lr*%3Nc=MUD}Va-9(l7ES^|Mb@Z*Qxfm`RP@fn@(paN~!hsQBd=q;>) z`X3JZE9-uq{&00x@zxs83$wk5X3rO%$ZaQXh7JRKQdcIhC^ZM=Ga3s{)0{<@V>Y_0 zk@JJQVusC-=ziAvM;64XYOCYKU2w!kY_qdEHM2~!Bo^#r7D|)s4@`Li=6o9PuO~Lm z=4qiRwR86<-#=f7SgkRZK09N%-8gY^wZ=%`klf?y_`S=#2tOMenwY*-PSjL!eIe(@ z%$)R>W7A-9u~nQ_D@~L@aUsGM7rEjMV?RfUu+-7s6Ax=8)wZa_q z`%MFXduvT*v&X&W*Igd1zqRDW**Mez$m2f$yDD$a3es`4Li?35wEX(z?E`zzstfs$ zo-}sM6Ce8483nC{bvwIqV1)WmyDP%8FXM7}n@pHZrlzR0j$UT|>qz2LuC8&?6d_^_ ztkKFlw`Lb`U-yXSAHzIAkK22W-C>WE1iMfiimo%jnb)VlydIW?4s3KLM{7(?X9KM+ zv$?vMhwU-HzKWgTjC-|5u0Fhf(kD5Bhp88JQLS&M%71l@C@tks>6(}e`T<%HrxCYp za6h ztr{L=2ENpNhrRCrfJ{pV1Dc*x8?JL=QJWvKG<>{FS+Jla0Ifsu@f!Hhbc@nLFa7gz z-AdVp|F8${XGWeO0P?w3)m1`&pw0dpsQn{^`QZqPGO57YApHq;aZRz%9sC3GkN*<~ zE$neGh6V@4l%(~%zddlp!#zwb)r0;N_zyJ5>cTvLC~4`fK4OzLfroM8T{xT6-js$< zz-&9(7;ip3IslPb8yc-?3?E$WCrq7}r@e$I=euK`ib?n3pv}j7mnybSti5}RIsb=2KN z{z1xl(B1{WTN};1cXe>ksP+%igr0wY_nP5tg~i3Kzt()JOfWa@us=NHg;R?=P5~ne z!s(?_=X0T&J~y15v*L7Y=rabS(~#4u__ua1U4eM*rwXWmz%@6&nccq<{QgbKS;lLj z%$If;Y)6Ap!F-mqyjme%#YNuf-W?t9yc^+gDR4P=J{6s5o>4{I11RkTrKou&E_tQW zr01Ds)br8HUDpNMak*CF{k8ET{Bhm#ZZQdB(d5>* zrii$NmR4Ft2(9iOT=N&ugV|0AG-Dcu-*`32r`42{^ZeaA^yIh;IBenUrjD5ge!W8F zYr2|N#8?$Ak!%q1Ovb>I@zy^yG;-ykh!f(jF@SD%OOx115NfMv!|nk}|gfR?~On;d@cjYn=cT$GG**ubs|_hKWk7c@Gvp&B8Bj7g%nm zr67hB<@o53kSF zthN{dBhHD#T1i!^G0a2|r7yt!SF?1F&=m3Iueaw%Oo;WdVxT`C95Uc^(IZ*lN|W*c z-t#U48F4XhCva)4CSLuy8au#CrT%0Wjiub3l5j=}IQ#i*9>#y@KyvN6X>H(_2<8g7Z=IR*1k4`A` zlf=FF2^8edSQ*Aaa%R>okC))+qYYt*TKLy8tfTY|GYH_7>I4r^_~*|nBlb^nyz+*q zBlU{X068=s99S)TN2&^FFXwUMJICJy9K0i>-?LewQa~!{%D*M#{~m$&a0(OT?J#0P zwt*HR0GsWrEh;=EI|3*?=+cRQBK6<%Q~%FN|30|g&rC&k<9W$~D{?#CgCqlBSwA6- zuOg08lnxuK+cOh6#z<2=<^-&Lfm&WDZ+SE4{Ooc`q3zuyYm;}600OOUcfM3Fru(>Y ztcA#Idvej^n&eEPVczFh{-Pb~tZcP03y>oLAm23V@!U#&Y4Uu3J|?+i(fm(RS* z2<4IY47bicub2&noGec5h73uT#UjN7SRhpGz{t7uJVS&zB<<)?FJ=OomE9l&MTTBKTEk6@)289_sBsUE36i&VD( zljM%d3MbLBgT@KW8f|UQ`P<4ua(v$wDkK;$f|D5;I8VQfkt$#KdYa(N9uq3}&jX%c zOTAB*gBzMu7k~-Aj7ky{O5+LZ!}ZRt7br8;<0rAcgkX>UghU%+QhR}ydQDUHtuE=3 zL@SR$#+)iLl5J*M3G;7wc@PK=6is}UJEyY7~IE-=MvQ`LExlHG~^&phc zHxsX<=;}CRf5{EMCDjcOj8G9O`NckfkX+ZfefcVIYfFD)BYz7^|D6^5U19&Nb_-)z z35xgJvp!%2Aog7;87lsd?Luknk`Br_LdV{z{vu#LAY(EnBO`R`5Rkb3_2u94yZ?5D z|LS%^yV6`WBQ^)^y79LzX$G{_?J3VdFm2StLI837RfJUKxyf;DHJ6|DpGj@6-~O8*$yCR>)fG!|jL(=iU{4j`jOW@$kafyc7lS&)QO8c)7pt!^6i!Lmx_R=zC;4 zE%e(`zo}Od+JK$c-k-d=WQQ7kcbw&4LL+-!`M4=*8yJ_`X@Q`pA7@$Hu_$C1WRQ+( zR*q-^mUX)~UGLSjSh}TJ>mAmR_A^^?!8s@}utI?G{_?_Lu*41SZrTspSliPp*PtY& z)l?fw-cy3RQZU=A<8!S#z~(}HT;o)+cur1}9x7j?x)umeU2?+({LuIAi<@Gbh4O~x zyl$J4ytgy?&ieRJ?#M?=W@brZE9QB~W?RzrN|S@4m{6b5y;eH&+-T9R$BJhw6`yOy z%x(=d74y>4Q*)$p!akLH#+YlMfV;dY8%sf>=8GyV5y`Lo9coT>wE}i+`exZCLSuF8 z`oXe@g@;iD-iONx=g5bm)+=VOi`%15iLt2{rt?q4+o_;WL~L_C2G`oPeIl(CBWcwM zJp$%)e<0oBQgU1sKP#nQGt-@qF$i}i)g@hj6HsD%wZLK0{9tm*37<%2rq8X3CJnIx z%Z?YvL`t#O9-(5M?)5oczo=+xXL`ytWvpb>y3DQOs3a**Rc)4O+I!mE4u}Iv0Ea8@ za}`qRoIb}Of?urMk8lsE%^10v-MUbE@>It8a5JtKbEr(Id{O$siPVa&>qmB?3F^o2&yfu}W=*^5#Stw$7Z^WqN zM_)XH=*>>AF7|h-2{x^cU>1L_gwUEyBZ^sw>d=dS@Qtbp}RHSy!=Rqrhw4 zbuj4D92~HKpS}S$d^~w!!MnTj@#sgpb6=qPleMdT?I|Y=QC*2esf*WVDl~e}ef5Jo zRJ^Bb4aS5}S4Et*?~W!;++r3dlipe~*Naj{mv(_396h*KvG+Rh?{1*`?6}ZlRS7*- zDb|r}Gzf6w+_3+yef>yT{lgfo&X+d*AsZ*7sv|UH7jNXCqj2v3t4#A_nW867*V+>l zAtHW?T17hn$npN+D8?=URjXtBf0T3nN88B~=Dzat9_|z=dfeY{NV&y{AEz7zfVP{L zx6ymJfm)zdt1c}cUD;6uMop6r>>h5q>5!zf1N5QdW+NJ{Gg1+P;1p8?>r=Q&%l2f8 z`be4(KiKPPU6sk@YPUZFpVD5K1LdfL)Ij*f{(OV!Sd)|Wh8?Y}(3^j{R!qHyg1>)D zQmphLn;+IaB|Y!&%C?WhM5W3(n9BjVBm0l<*Pn-~2e&STp8r?uQwRK<(60Io5$0(c zFweGHk3Ewzy6$&6mfI#>-(SG#(H;RFzSNa#rXJ==o3&QS%!%nLuu^Wmf3upKZllnh z^=hTV=e`L|uW$K@iB7M)mO*l7kJS&~Y=#wk<1KW~vEt{1o}7^tefOSeV8L`*W_Hoq zT)mwUX%Wh|j%f!RkTmsOha+|v@Y+*K`Lbp25~L+XNXuH2_7!lOu!A;P1Em<(mw*TF z$d6I+jx4`>C#BC_!U~(_oDt`gn3L@8gj@KD~!^139kE2p!CPUsn2Lr77YNIV-75trI}SLNi2J8QtLt?l%sWhGt3pyz z+8#UWUT0Qro49cM=Hu$wH!^aH{Krh9A|~w}d|ox~)KJ71Wdp$U?HB+|vb{@jw_(f~ z=M9tf`=@-Qr1Zj_-RC2OtR@5I=nWxvTGj7oj+J!HwFx|4# z8ozeL)Nk6|^OUjNi()&z&Y3tZj$VnKY-K_LBewKb4(A1s2p|>#q&a*U;C3SUIRAyH zafy;({DQk;3bSTd`Q2}3jt!3t1af@GB_$Z>>$+OMWT(LxQ zv0U_~Q%X}>@XTYH3ex$_@ToVTEGIVu35c7WfkJBsv8KJTGr&$8{75bWK<7e|9 zf>G9?z+1noHkP>O5+%;f)9Im+KK^dEVi1{X7x?BMOO z5^RQk!MRMpD0}|~uCf0bdc^VPKSFui$vK^;sOqmHuNCa>&ele|vjOj3 ztAt=woD@L;(bm%E&4GdZ$H1}3F6IeIa7}>W#K!{+hiDk(w`@sDw{zC1$cJwq2S-*+ zug)sh`TAbYMha2G|1=g?H&L2_aeEC#-v%J;r{F7t4C}?gfsLyB{$IbAc$!`NrL1f* z{rvIfn8G`&b^gGuj5~|C!`n+JB56T46RS&7!RsF`Pokz?d)c-psm{*mO^m`#ZB=3u z(g7Zrd0Y~jU6A+-U<@f&1^zp7l=5whK zEMa+urHFwJ0}Yc58@6Fl{q;&K|JF+ujdscAUOan z!3UY^m89LfVYe4p4kGm5nSBQUK72@IJuy1fCE2=F;iLvbL(#69fWb0i$ErfG7}Znh zwsu;OHNdc3!rp7)H~byeTeeO9=vAjJ^^d~+00}qTTR9BKcVZML44s2sg?C%alY75n>ywG5y~;M)@*nzAtm?Cu}6qW|>G|B(;<-tnH|{Cp1BO`F3q?%-%~*+X|P>*XWdq6 z5Kfzbdvv+o-b1~^UJFDE617wW!7lfj?>FJ9BfHWiH;YLV*!4k%1K^+aRkOXvMZi^l z3?I@7Tl;F{Cj@J6$x<^7XM})x!~lHfvT%|viu~Al<}B$1M23jN@I~-2e_G>hkJ_Js znC6c{`Qc4bIsU@DTRGh(t2?uVKIlb$XT?h1E6*KOsRUSXe6)5UnKHj+b8)3%)L>_& zi6YzIIVldv3O#LQ%nmG6c|vu=d^LUAJ|vq)@tOr+fC9{qg}ou9v`&ZNyGgbO2ZrO| z%M<=U;jH3hp?tznZ$mlZEev?)%dW}Qs>cUu%1(Ai-33bqL}rsP360UvxNZ1 zDDZ6USm_nS`1e2mq~B<4P&N*<(kMn#+EY8loipPcwU#M}7@Vz#P|T0gP%;smCdjHD zB^DT;IB8(junMmF4Up^=GqCIG5#%VD2u?S5?DHEQvfM)xE)puM%DZm~oDry%y@ZUD z{T3I1UHv({kC?E>hTa^^dJ6+~^@_kH9&3)!0s0ia6|^!iP~$cS=^kDZu*w8^Lhc=~ zsiTMQzs^xTV}q52y%o6xz@^%ds{o1%ar$Sp(#U1 z5BGtN-nau7x88oZvMH)_EVKH~R>-96_XShoral0h>K=-gY)t?b;QP5r5tej?-+u?b z?<5$uoCOEDX!?x5-Nh0X2Opg%WSw3C2L%K)8v=(rj^f{H?ws@ju0r@kCbPGxj-5^K zJymwNj{W2SYK<P9q0zjdO2of*LPxQd!S4f<2-Xd&zOpRc4I=;;7vsh+XBoT`@yMA^S)rcF=>v(6KOhi-?%&Wts;)06>v`3(5B zEGKNsOn}{*PgI7_{q$oZ_d9UdO}7CA(kUF~5ac$^*Z;92 zAWpS7>*Z2a?#UtKQ z)pC`Zzk1I-;#2{7-tqLb3H{QXFe>3=OO=VD#a3IhxHIn(P}j-TJwR-HSVlKM18DN~ zRN0cu7?TnT6g4lKiMz~~`mtE@iRO-~k$o^W7I{)Q zmKRMiBwwIh9;9ag=BGNa@zTj;9*8ud-u^eL}7N+@A{Kt&3cS6C_W$Ivm`Dz7N>`)5eM3HuqDtBB zlh_>HngGYoBEqjIcIGs}zI87CSn5lBHra;QXdjUC@CnDae~n90x>eE_`0YVavX^ zF-2~=`CV16!?J2|KNfnKHqo{WXO$?P?Kx%cIANl%Ij03l1t3c(VDXcMuy>C>0WhWf z7TZ-AMFbG;B?PG^yq1t?ic2vekpAK&Y3oMi40~!4O!l)2)A@t?lMlZq}zixtTYs2yOaRQxRbQ-F@t;Mn>X^+d^xFiH!hz(Jp%R~A$(LgfhdtY zxH~S83&YriGMzO5Thh_(3V=CG~n%lKbJ z52uM~r-bKlCqz?`72)4+qY5nZK4oM-(IEfQv~IS>9_BPLGbf~NJ%s(LR{oRqd1nd} zgh|Pub5tc+{Bj>+5I(0tG>K_E<`iaWj9BPE!)4a&u4g5neN3E)KGZ(j*S6#3{lNf`{ ziL-zvE&%ZOZf3?kwa*G4!vTuL8Lm3*ZIZm{Zmse2`S~X@ae;neOe${Jobdm@flnXLI7JdrK0%cxIi{r1`ek6?8I z4yFC2k5&(2>GNezg0^byv8Izd*1s)MRDU#b9F7`)eBs{y8u`vUtbK=d7qKeQa?Kt+ zV;4jCKo`x>nX?9bt<)s?+dtMJ`fd8V2P$ zYEA|jVpitZ(%8bROo1$LM2{rJ*}-@RhWPgDruA*N^3JE(CBYpkwq7k_#fPmhaXM|^ z4?G=b3&sKTdjJ&FMMRIJmHh;gIcc(#k*OaT(Guv*Adr`=qF)XZ9UAHjOTGKaSE_kM434=)W+&Hef+@dAu zc(w~R`JI$m)xJ~3vscDEDw?u_aogX`&nu*_jIhk3<=iTj>3)MFxA%kBl|wViKhwA) zY5q$3+Jsya}Qe4wa>ciDqj*M021#*m(BE^48( z#?p1!bjD#nxjaY4oS^KWchpx-=g)FB-)nZyV~srB5{6?9Ki4m5T^vQIAfPrxk{b1r zlEK-D2GXX5XwLMQLRzrR#&Pmg5S{|%1)I#zg&ZLiy7y-9*B5kYkj`mRKa-UmU=a8q z^GSObwria>lECX;U0QPrUJ)F2(d0aU&M(uE;WVWL8`}j(D?Wb(?5N}089^GZnQ5vr zRzl8kwrw_>Hq+__hmmuMPm|gv6IQyan1~^lU?*<2DM8-tepBTI)=+&VFGG+iciyW9 zZ>r!nTx(IdtH@48%AeDqiH*EL8}|giD(v3F8sGxt)JFWX3hjRW8Ygd_2>GNK(3#xv?*UrGWY74^;*DR|k! zeLqcdYjazuuJK&u%-HdZNzHklZmc<{L}{7#ge5!EyhEsr1I@R>TC~ogQr~i0xy2FE zWv*4qI$LfdAK@$-ubHqpkDcU$JqciPTfy76OB-m|bt%G739zN~K}eY*fPGec~u?)#P3fd6e~P>_ zdNE4H9gCuEcdm!d<7X!Mb{D6bY~a!@GoY9 zJZ}73y*R;xv!68y#_M$E4Nd|`Ja}y^qhbD*VMqr>VBhD?P+iBuk4_Q4ywsKZxfrytns=GRJE+n(wuD9f103 zUzWzXs2sM%@N!~h`X-)D))$Uz(~qwNUNrkvY^bZ*l}pUg51U;iq*=6QX|uCfgvIC_ z<&pXjyN3RyfAXhFve^z$yt{E4DB=188P(-h|IocQHiYE3V1OTso-Ay)gANgkJe(Q?BcpF3_>e5cVtC|M>sezpEvh3)>GthR@yz a Date: Tue, 16 Aug 2022 12:18:53 +0200 Subject: [PATCH 011/101] OP-3723 - resize saved images in PS for ffmpeg Ffmpeg cannot handle pictures higher than 16384x16384. Uses PIL to resize to max size(with aspect ratio). In the future this plugin should be refactored (to use general ExtractThumbnail and ExtractReview). --- .../plugins/publish/extract_review.py | 55 +++++++++++++++++-- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index d076610ead..7f78a46527 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -1,5 +1,6 @@ import os import shutil +from PIL import Image import openpype.api import openpype.lib @@ -8,10 +9,17 @@ from openpype.hosts.photoshop import api as photoshop class ExtractReview(openpype.api.Extractor): """ - Produce a flattened or sequence image file from all 'image' instances. + Produce a flattened or sequence image files from all 'image' instances. If no 'image' instance is created, it produces flattened image from all visible layers. + + It creates review, thumbnail and mov representations. + + 'review' family could be used in other steps as a reference, as it + contains flattened image by default. (Eg. artist could load this + review as a single item and see full image. In most cases 'image' + family is separated by layers to better usage in animation or comp.) """ label = "Extract Review" @@ -49,7 +57,7 @@ class ExtractReview(openpype.api.Extractor): "stagingDir": staging_dir, "tags": self.jpg_options['tags'], }) - + processed_img_names = img_list else: self.log.info("Extract layers to flatten image.") img_list = self._saves_flattened_layers(staging_dir, layers) @@ -57,26 +65,33 @@ class ExtractReview(openpype.api.Extractor): instance.data["representations"].append({ "name": "jpg", "ext": "jpg", - "files": img_list, + "files": img_list, # cannot be [] for single frame "stagingDir": staging_dir, "tags": self.jpg_options['tags'] }) + processed_img_names = [img_list] ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") instance.data["stagingDir"] = staging_dir - # Generate thumbnail. + source_files_pattern = os.path.join(staging_dir, + self.output_seq_filename) + source_files_pattern = self._check_and_resize(processed_img_names, + source_files_pattern, + staging_dir) + # Generate thumbnail thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") self.log.info(f"Generate thumbnail {thumbnail_path}") args = [ ffmpeg_path, "-y", - "-i", os.path.join(staging_dir, self.output_seq_filename), + "-i", source_files_pattern, "-vf", "scale=300:-1", "-vframes", "1", thumbnail_path ] + self.log.debug("thumbnail args:: {}".format(args)) output = openpype.lib.run_subprocess(args) instance.data["representations"].append({ @@ -94,11 +109,12 @@ class ExtractReview(openpype.api.Extractor): args = [ ffmpeg_path, "-y", - "-i", os.path.join(staging_dir, self.output_seq_filename), + "-i", source_files_pattern, "-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-vframes", str(img_number), mov_path ] + self.log.debug("mov args:: {}".format(args)) output = openpype.lib.run_subprocess(args) self.log.debug(output) instance.data["representations"].append({ @@ -120,6 +136,33 @@ class ExtractReview(openpype.api.Extractor): self.log.info(f"Extracted {instance} to {staging_dir}") + def _check_and_resize(self, processed_img_names, source_files_pattern, + staging_dir): + """Check if saved image could be used in ffmpeg. + + Ffmpeg has max size 16384x16384. Saved image(s) must be resized to be + used as a source for thumbnail or review mov. + """ + max_ffmpeg_size = 16384 + first_url = os.path.join(staging_dir, processed_img_names[0]) + with Image.open(first_url) as im: + width, height = im.size + + if width > max_ffmpeg_size or height > max_ffmpeg_size: + resized_dir = os.path.join(staging_dir, "resized") + os.mkdir(resized_dir) + source_files_pattern = os.path.join(resized_dir, + self.output_seq_filename) + for file_name in processed_img_names: + source_url = os.path.join(staging_dir, file_name) + with Image.open(source_url) as res_img: + # 'thumbnail' automatically keeps aspect ratio + res_img.thumbnail((max_ffmpeg_size, max_ffmpeg_size), + Image.ANTIALIAS) + res_img.save(os.path.join(resized_dir, file_name)) + + return source_files_pattern + def _get_image_path_from_instances(self, instance): img_list = [] From db6f46895b9c2a3659bfb5803705388b7d2f7dfd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 17 Aug 2022 16:01:31 +0200 Subject: [PATCH 012/101] OP-3723 - remove PIL limit High resolution could trigger " could be decompression bomb DOS attack". --- openpype/hosts/photoshop/plugins/publish/extract_review.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 7f78a46527..151440b914 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -144,6 +144,7 @@ class ExtractReview(openpype.api.Extractor): used as a source for thumbnail or review mov. """ max_ffmpeg_size = 16384 + Image.MAX_IMAGE_PIXELS = None first_url = os.path.join(staging_dir, processed_img_names[0]) with Image.open(first_url) as im: width, height = im.size From ebdf8a348a3eb4e34162564c719a872b5e30b71e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 17 Aug 2022 17:41:23 +0200 Subject: [PATCH 013/101] OP-3723 - changed max limit Official 16384x16384 actually didn't work because int overflow. 16000 tested and worked. --- openpype/hosts/photoshop/plugins/publish/extract_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 151440b914..64decbb957 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -143,7 +143,8 @@ class ExtractReview(openpype.api.Extractor): Ffmpeg has max size 16384x16384. Saved image(s) must be resized to be used as a source for thumbnail or review mov. """ - max_ffmpeg_size = 16384 + # 16384x16384 actually didn't work because int overflow + max_ffmpeg_size = 16000 Image.MAX_IMAGE_PIXELS = None first_url = os.path.join(staging_dir, processed_img_names[0]) with Image.open(first_url) as im: From ec405eb9130c6fe9c8b13dde34983fb43341507d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 11:52:46 +0200 Subject: [PATCH 014/101] prepared some classes to handle settings locks --- openpype/settings/handlers.py | 162 ++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 15ae2351fd..8d13875d0b 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -22,6 +22,168 @@ from .constants import ( ) +class SettingsStateInfo: + """Helper state information for Settings state. + + Is used to hold information about last save and last opened UI. Keep + information about the time when that happened and on which machine under + which user. + + To create currrent machine and time information use 'create_new' method. + """ + + timestamp_format = "%Y-%m-%d %H:%M:%S.%f" + + def __init__( + self, timestamp, hostname, hostip, username, system_name, local_id + ): + self.timestamp = timestamp + self._timestamp_obj = datetime.datetime.strptime( + timestamp, self.timestamp_format + ) + self.hostname = hostname + self.hostip = hostip + self.username = username + self.system_name = system_name + self.local_id = local_id + + def copy(self): + return self.from_data(self.to_data()) + + @property + def timestamp_obj(self): + return self._timestamp_obj + + @classmethod + def create_new(cls): + """Create information about this machine for current time.""" + + from openpype.lib.pype_info import get_workstation_info + + now = datetime.datetime.now() + workstation_info = get_workstation_info() + + return cls( + now.strftime(cls.timestamp_format), + workstation_info["hostname"], + workstation_info["hostip"], + workstation_info["username"], + workstation_info["system_name"], + workstation_info["local_id"] + ) + + @classmethod + def from_data(cls, data): + """Create object from data.""" + + return cls( + data["timestamp"], + data["hostname"], + data["hostip"], + data["username"], + data["system_name"], + data["local_id"] + ) + + def to_data(self): + return { + "timestamp": self.timestamp, + "hostname": self.hostname, + "hostip": self.hostip, + "username": self.username, + "system_name": self.system_name, + "local_id": self.local_id, + } + + def __eq__(self, other): + if not isinstance(other, SettingsStateInfo): + return False + + if other.timestamp_obj != self.timestamp_obj: + return False + + return ( + self.hostname == other.hostname + and self.hostip == other.hostip + and self.username == other.username + and self.system_name == other.system_name + and self.local_id == other.local_id + ) + + +class SettingsState: + """State of settings with last saved and last opened. + + Args: + openpype_version (str): OpenPype version string. + settings_type (str): Type of settings. System or project settings. + last_saved_info (Union[None, SettingsStateInfo]): Information about + machine and time when were settings saved last time. + last_opened_info (Union[None, SettingsStateInfo]): This is settings UI + specific information similar to last saved describes who had opened + settings as last. + project_name (Union[None, str]): Identifier for project settings. + """ + + def __init__( + self, + openpype_version, + settings_type, + last_saved_info, + last_opened_info, + project_name=None + ): + self.openpype_version = openpype_version + self.settings_type = settings_type + self.last_saved_info = last_saved_info + self.last_opened_info = last_opened_info + self.project_name = project_name + + def __eq__(self, other): + if not isinstance(other, SettingsState): + return False + + return ( + self.openpype_version == other.openpype_version + and self.settings_type == other.settings_type + and self.last_saved_info == other.last_saved_info + and self.last_opened_info == other.last_opened_info + and self.project_name == other.project_name + ) + + def copy(self): + return self.__class__( + self.openpype_version, + self.settings_type, + self.last_saved_info.copy(), + self.last_opened_info.copy(), + self.project_name + ) + + def on_save(self, openpype_version): + self.openpype_version = openpype_version + self.last_saved_info = SettingsStateInfo.create_new() + + @classmethod + def from_document(cls, openpype_version, settings_type, document): + document = document or {} + last_saved_info = document.get("last_saved_info") + if last_saved_info: + last_saved_info = SettingsStateInfo.from_data(last_saved_info) + + last_opened_info = document.get("last_opened_info") + if last_opened_info: + last_opened_info = SettingsStateInfo.from_data(last_opened_info) + + return cls( + openpype_version, + settings_type, + last_saved_info, + last_opened_info, + document.get("project_name") + ) + + @six.add_metaclass(ABCMeta) class SettingsHandler: @abstractmethod From 17957a760c4c75ded01e944605f164ce058b252c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 11:53:24 +0200 Subject: [PATCH 015/101] 'update_data' and 'update_from_document' always require version --- openpype/settings/handlers.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 8d13875d0b..5a0a30e4a6 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -453,13 +453,12 @@ class CacheValues: return {} return copy.deepcopy(self.data) - def update_data(self, data, version=None): + def update_data(self, data, version): self.data = data self.creation_time = datetime.datetime.now() - if version is not None: - self.version = version + self.version = version - def update_from_document(self, document, version=None): + def update_from_document(self, document, version): data = {} if document: if "data" in document: @@ -468,9 +467,9 @@ class CacheValues: value = document["value"] if value: data = json.loads(value) + self.data = data - if version is not None: - self.version = version + self.version = version def to_json_string(self): return json.dumps(self.data or {}) @@ -1567,7 +1566,7 @@ class MongoLocalSettingsHandler(LocalSettingsHandler): """ data = data or {} - self.local_settings_cache.update_data(data) + self.local_settings_cache.update_data(data, None) self.collection.replace_one( { @@ -1590,6 +1589,6 @@ class MongoLocalSettingsHandler(LocalSettingsHandler): "site_id": self.local_site_id }) - self.local_settings_cache.update_from_document(document) + self.local_settings_cache.update_from_document(document, None) return self.local_settings_cache.data_copy() From 0dc4f1a78622edf932d8623b8ecafdae8c18afc7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 11:53:49 +0200 Subject: [PATCH 016/101] cache also can have settings state --- openpype/settings/handlers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 5a0a30e4a6..0ee1f74692 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -447,6 +447,7 @@ class CacheValues: self.data = None self.creation_time = None self.version = None + self.settings_state = None def data_copy(self): if not self.data: @@ -458,6 +459,9 @@ class CacheValues: self.creation_time = datetime.datetime.now() self.version = version + def update_settings_state(self, settings_state): + self.settings_state = settings_state + def update_from_document(self, document, version): data = {} if document: From 0e6ff4a21d224ed188cdf076a4fd00a1a8f696ea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 15:59:51 +0200 Subject: [PATCH 017/101] cache can be set to outdated --- openpype/settings/handlers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 0ee1f74692..f6e81a7d0a 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -485,6 +485,9 @@ class CacheValues: delta = (datetime.datetime.now() - self.creation_time).seconds return delta > self.cache_lifetime + def set_outdated(self): + self.create_time = None + class MongoSettingsHandler(SettingsHandler): """Settings handler that use mongo for storing and loading of settings.""" From 4a322f1ceda11843c13627ee5c1f1d9070c82f12 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 16:00:23 +0200 Subject: [PATCH 018/101] removed 'SettingsState' and kept only 'SettingsStateInfo' --- openpype/settings/handlers.py | 152 +++++++++++++++------------------- 1 file changed, 66 insertions(+), 86 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index f6e81a7d0a..e4b4bc3dc4 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -23,11 +23,11 @@ from .constants import ( class SettingsStateInfo: - """Helper state information for Settings state. + """Helper state information about some settings state. - Is used to hold information about last save and last opened UI. Keep + Is used to hold information about last saved and last opened UI. Keep information about the time when that happened and on which machine under - which user. + which user and on which openpype version. To create currrent machine and time information use 'create_new' method. """ @@ -35,12 +35,28 @@ class SettingsStateInfo: timestamp_format = "%Y-%m-%d %H:%M:%S.%f" def __init__( - self, timestamp, hostname, hostip, username, system_name, local_id + self, + openpype_version, + settings_type, + project_name, + timestamp, + hostname, + hostip, + username, + system_name, + local_id ): + self.openpype_version = openpype_version + self.settings_type = settings_type + self.project_name = project_name + + timestamp_obj = None + if timestamp: + timestamp_obj = datetime.datetime.strptime( + timestamp, self.timestamp_format + ) self.timestamp = timestamp - self._timestamp_obj = datetime.datetime.strptime( - timestamp, self.timestamp_format - ) + self.timestamp_obj = timestamp_obj self.hostname = hostname self.hostip = hostip self.username = username @@ -50,12 +66,8 @@ class SettingsStateInfo: def copy(self): return self.from_data(self.to_data()) - @property - def timestamp_obj(self): - return self._timestamp_obj - @classmethod - def create_new(cls): + def create_new(cls, openpype_version, settings_type, project_name): """Create information about this machine for current time.""" from openpype.lib.pype_info import get_workstation_info @@ -64,6 +76,9 @@ class SettingsStateInfo: workstation_info = get_workstation_info() return cls( + openpype_version, + settings_type, + project_name, now.strftime(cls.timestamp_format), workstation_info["hostname"], workstation_info["hostip"], @@ -77,6 +92,9 @@ class SettingsStateInfo: """Create object from data.""" return cls( + data["openpype_version"], + data["settings_type"], + data["project_name"], data["timestamp"], data["hostname"], data["hostip"], @@ -86,6 +104,40 @@ class SettingsStateInfo: ) def to_data(self): + data = self.to_document_data() + data.update({ + "openpype_version": self.openpype_version, + "settings_type": self.settings_type, + "project_name": self.project_name + }) + return data + + @classmethod + def from_document(cls, openpype_version, settings_type, document): + document = document or {} + project_name = document.get("project_name") + last_saved_info = document.get("last_saved_info") + if last_saved_info: + copy_last_saved_info = copy.deepcopy(last_saved_info) + copy_last_saved_info.update({ + "openpype_version": openpype_version, + "settings_type": settings_type, + "project_name": project_name, + }) + return cls.from_data(copy_last_saved_info) + return cls( + openpype_version, + settings_type, + project_name, + None, + None, + None, + None, + None, + None + ) + + def to_document_data(self): return { "timestamp": self.timestamp, "hostname": self.hostname, @@ -103,7 +155,8 @@ class SettingsStateInfo: return False return ( - self.hostname == other.hostname + self.openpype_version == other.openpype_version + and self.hostname == other.hostname and self.hostip == other.hostip and self.username == other.username and self.system_name == other.system_name @@ -111,79 +164,6 @@ class SettingsStateInfo: ) -class SettingsState: - """State of settings with last saved and last opened. - - Args: - openpype_version (str): OpenPype version string. - settings_type (str): Type of settings. System or project settings. - last_saved_info (Union[None, SettingsStateInfo]): Information about - machine and time when were settings saved last time. - last_opened_info (Union[None, SettingsStateInfo]): This is settings UI - specific information similar to last saved describes who had opened - settings as last. - project_name (Union[None, str]): Identifier for project settings. - """ - - def __init__( - self, - openpype_version, - settings_type, - last_saved_info, - last_opened_info, - project_name=None - ): - self.openpype_version = openpype_version - self.settings_type = settings_type - self.last_saved_info = last_saved_info - self.last_opened_info = last_opened_info - self.project_name = project_name - - def __eq__(self, other): - if not isinstance(other, SettingsState): - return False - - return ( - self.openpype_version == other.openpype_version - and self.settings_type == other.settings_type - and self.last_saved_info == other.last_saved_info - and self.last_opened_info == other.last_opened_info - and self.project_name == other.project_name - ) - - def copy(self): - return self.__class__( - self.openpype_version, - self.settings_type, - self.last_saved_info.copy(), - self.last_opened_info.copy(), - self.project_name - ) - - def on_save(self, openpype_version): - self.openpype_version = openpype_version - self.last_saved_info = SettingsStateInfo.create_new() - - @classmethod - def from_document(cls, openpype_version, settings_type, document): - document = document or {} - last_saved_info = document.get("last_saved_info") - if last_saved_info: - last_saved_info = SettingsStateInfo.from_data(last_saved_info) - - last_opened_info = document.get("last_opened_info") - if last_opened_info: - last_opened_info = SettingsStateInfo.from_data(last_opened_info) - - return cls( - openpype_version, - settings_type, - last_saved_info, - last_opened_info, - document.get("project_name") - ) - - @six.add_metaclass(ABCMeta) class SettingsHandler: @abstractmethod From 20509b4610250c6d6725c50659b0ce3a065b0e92 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 16:02:46 +0200 Subject: [PATCH 019/101] changed 'settings_state' to 'last_saved_info' in cache --- openpype/settings/handlers.py | 44 ++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index e4b4bc3dc4..e34a4c3540 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -368,7 +368,7 @@ class SettingsHandler: """OpenPype versions that have any studio project anatomy overrides. Returns: - list: OpenPype versions strings. + List[str]: OpenPype versions strings. """ pass @@ -379,7 +379,7 @@ class SettingsHandler: """OpenPype versions that have any studio project settings overrides. Returns: - list: OpenPype versions strings. + List[str]: OpenPype versions strings. """ pass @@ -393,8 +393,39 @@ class SettingsHandler: project_name(str): Name of project. Returns: - list: OpenPype versions strings. + List[str]: OpenPype versions strings. """ + + pass + + @abstractmethod + def get_system_last_saved_info(self): + """State of last system settings overrides at the moment when called. + + This method must provide most recent data so using cached data is not + the way. + + Returns: + SettingsStateInfo: Information about system settings overrides. + """ + + pass + + @abstractmethod + def get_project_last_saved_info(self, project_name): + """State of last project settings overrides at the moment when called. + + This method must provide most recent data so using cached data is not + the way. + + Args: + project_name (Union[None, str]): Project name for which state + should be returned. + + Returns: + SettingsStateInfo: Information about project settings overrides. + """ + pass @@ -427,7 +458,7 @@ class CacheValues: self.data = None self.creation_time = None self.version = None - self.settings_state = None + self.last_saved_info = None def data_copy(self): if not self.data: @@ -439,8 +470,8 @@ class CacheValues: self.creation_time = datetime.datetime.now() self.version = version - def update_settings_state(self, settings_state): - self.settings_state = settings_state + def update_last_saved_info(self, last_saved_info): + self.last_saved_info = last_saved_info def update_from_document(self, document, version): data = {} @@ -1288,6 +1319,7 @@ class MongoSettingsHandler(SettingsHandler): self.project_anatomy_cache[project_name].update_from_document( document, version ) + else: project_doc = get_project(project_name) self.project_anatomy_cache[project_name].update_data( From ba434d5f713de2eb478bd1cea533163d482d4033 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 16:03:49 +0200 Subject: [PATCH 020/101] changed how update of settings happens --- openpype/settings/handlers.py | 49 +++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index e34a4c3540..43b1d37c34 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -696,20 +696,28 @@ class MongoSettingsHandler(SettingsHandler): system_settings_data ) - # Store system settings - self.collection.replace_one( + system_settings_doc = self.collection.find_one( { "type": self._system_settings_key, "version": self._current_version }, - { - "type": self._system_settings_key, - "data": system_settings_data, - "version": self._current_version - }, - upsert=True + {"_id": True} ) + # Store system settings + new_system_settings_doc = { + "type": self._system_settings_key, + "version": self._current_version, + "data": system_settings_data, + } + if not system_settings_doc: + self.collections.insert_one(new_system_settings_doc) + else: + self.collections.update_one( + {"_id": system_settings_doc["_id"]}, + {"$set": new_system_settings_doc} + ) + # Store global settings self.collection.replace_one( { @@ -844,26 +852,33 @@ class MongoSettingsHandler(SettingsHandler): def _save_project_data(self, project_name, doc_type, data_cache): is_default = bool(project_name is None) - replace_filter = { + query_filter = { "type": doc_type, "is_default": is_default, "version": self._current_version } - replace_data = { + last_saved_info = data_cache.last_saved_info + new_project_settings_doc = { "type": doc_type, "data": data_cache.data, "is_default": is_default, - "version": self._current_version + "version": self._current_version, } if not is_default: - replace_filter["project_name"] = project_name - replace_data["project_name"] = project_name + query_filter["project_name"] = project_name + new_project_settings_doc["project_name"] = project_name - self.collection.replace_one( - replace_filter, - replace_data, - upsert=True + project_settings_doc = self.collection.find_one( + query_filter, + {"_id": True} ) + if project_settings_doc: + self.collection.update_one( + {"_id": project_settings_doc["_id"]}, + new_project_settings_doc + ) + else: + self.collection.insert_one(new_project_settings_doc) def _get_versions_order_doc(self, projection=None): # TODO cache From 6c9d6b3865cfdce385aba868d61b9cb09985ab88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 16:05:15 +0200 Subject: [PATCH 021/101] added helper methods for query of override documents --- openpype/settings/handlers.py | 57 +++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 43b1d37c34..6080b5e77f 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -1205,18 +1205,7 @@ class MongoSettingsHandler(SettingsHandler): globals_document = self.collection.find_one({ "type": GLOBAL_SETTINGS_KEY }) - document = ( - self._get_studio_system_settings_overrides_for_version() - ) - if document is None: - document = self._find_closest_system_settings() - - version = None - if document: - if document["type"] == self._system_settings_key: - version = document["version"] - else: - version = LEGACY_SETTINGS_VERSION + document, version = self._get_system_settings_overrides_doc() merged_document = self._apply_global_settings( document, globals_document @@ -1232,21 +1221,27 @@ class MongoSettingsHandler(SettingsHandler): return data, cache.version return data + def _get_system_settings_overrides_doc(self): + document = ( + self._get_studio_system_settings_overrides_for_version() + ) + if document is None: + document = self._find_closest_system_settings() + + version = None + if document: + if document["type"] == self._system_settings_key: + version = document["version"] + else: + version = LEGACY_SETTINGS_VERSION + + return document, version + def _get_project_settings_overrides(self, project_name, return_version): if self.project_settings_cache[project_name].is_outdated: - document = self._get_project_settings_overrides_for_version( + document, version = self._get_project_settings_overrides_doc( project_name ) - if document is None: - document = self._find_closest_project_settings(project_name) - - version = None - if document: - if document["type"] == self._project_settings_key: - version = document["version"] - else: - version = LEGACY_SETTINGS_VERSION - self.project_settings_cache[project_name].update_from_document( document, version ) @@ -1257,6 +1252,22 @@ class MongoSettingsHandler(SettingsHandler): return data, cache.version return data + def _get_project_settings_overrides_doc(self, project_name): + document = self._get_project_settings_overrides_for_version( + project_name + ) + if document is None: + document = self._find_closest_project_settings(project_name) + + version = None + if document: + if document["type"] == self._project_settings_key: + version = document["version"] + else: + version = LEGACY_SETTINGS_VERSION + + return document, version + def get_studio_project_settings_overrides(self, return_version): """Studio overrides of default project settings.""" return self._get_project_settings_overrides(None, return_version) From 8f121275bdbb08eb686ce482f529b97c96dfe1cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 16:06:11 +0200 Subject: [PATCH 022/101] implemented methods to get last saved information --- openpype/settings/handlers.py | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 6080b5e77f..af2bf104de 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -688,6 +688,15 @@ class MongoSettingsHandler(SettingsHandler): # Update cache self.system_settings_cache.update_data(data, self._current_version) + last_saved_info = SettingsStateInfo.create_new( + self._current_version, + SYSTEM_SETTINGS_KEY, + None + ) + self.system_settings_cache.update_last_saved_info( + last_saved_info + ) + # Get copy of just updated cache system_settings_data = self.system_settings_cache.data_copy() @@ -709,6 +718,7 @@ class MongoSettingsHandler(SettingsHandler): "type": self._system_settings_key, "version": self._current_version, "data": system_settings_data, + "last_saved_info": last_saved_info.to_document_data() } if not system_settings_doc: self.collections.insert_one(new_system_settings_doc) @@ -749,6 +759,14 @@ class MongoSettingsHandler(SettingsHandler): data_cache = self.project_settings_cache[project_name] data_cache.update_data(overrides, self._current_version) + last_saved_info = SettingsStateInfo.create_new( + self._current_version, + PROJECT_SETTINGS_KEY, + project_name + ) + + data_cache.update_last_saved_info(last_saved_info) + self._save_project_data( project_name, self._project_settings_key, data_cache ) @@ -863,6 +881,7 @@ class MongoSettingsHandler(SettingsHandler): "data": data_cache.data, "is_default": is_default, "version": self._current_version, + "last_saved_info": last_saved_info.to_data() } if not is_default: query_filter["project_name"] = project_name @@ -1207,6 +1226,9 @@ class MongoSettingsHandler(SettingsHandler): }) document, version = self._get_system_settings_overrides_doc() + last_saved_info = SettingsStateInfo.from_document( + version, SYSTEM_SETTINGS_KEY, document + ) merged_document = self._apply_global_settings( document, globals_document ) @@ -1214,6 +1236,9 @@ class MongoSettingsHandler(SettingsHandler): self.system_settings_cache.update_from_document( merged_document, version ) + self.system_settings_cache.update_last_saved_info( + last_saved_info + ) cache = self.system_settings_cache data = cache.data_copy() @@ -1237,6 +1262,13 @@ class MongoSettingsHandler(SettingsHandler): return document, version + def get_system_last_saved_info(self): + # Make sure settings are recaches + self.system_settings_cache.set_outdated() + self.get_studio_system_settings_overrides(False) + + return self.system_settings_cache.last_saved_info.copy() + def _get_project_settings_overrides(self, project_name, return_version): if self.project_settings_cache[project_name].is_outdated: document, version = self._get_project_settings_overrides_doc( @@ -1245,6 +1277,12 @@ class MongoSettingsHandler(SettingsHandler): self.project_settings_cache[project_name].update_from_document( document, version ) + last_saved_info = SettingsStateInfo.from_document( + version, PROJECT_SETTINGS_KEY, document + ) + self.project_settings_cache[project_name].update_last_saved_info( + last_saved_info + ) cache = self.project_settings_cache[project_name] data = cache.data_copy() @@ -1268,6 +1306,13 @@ class MongoSettingsHandler(SettingsHandler): return document, version + def get_project_last_saved_info(self, project_name): + # Make sure settings are recaches + self.project_settings_cache[project_name].set_outdated() + self._get_project_settings_overrides(project_name, False) + + return self.project_settings_cache[project_name].last_saved_info.copy() + def get_studio_project_settings_overrides(self, return_version): """Studio overrides of default project settings.""" return self._get_project_settings_overrides(None, return_version) From 9ea46a2a3690df28c2781cdd0919ed44e0d275fc Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 16:45:37 +0200 Subject: [PATCH 023/101] make available api functions in settings to have access to lock information and last saved information --- openpype/settings/handlers.py | 127 +++++++++++++++++++++++++++++++++- openpype/settings/lib.py | 25 +++++++ 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index af2bf104de..3dc33503ea 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -67,7 +67,9 @@ class SettingsStateInfo: return self.from_data(self.to_data()) @classmethod - def create_new(cls, openpype_version, settings_type, project_name): + def create_new( + cls, openpype_version, settings_type=None, project_name=None + ): """Create information about this machine for current time.""" from openpype.lib.pype_info import get_workstation_info @@ -112,6 +114,20 @@ class SettingsStateInfo: }) return data + @classmethod + def create_new_empty(cls, openpype_version, settings_type=None): + return cls( + openpype_version, + settings_type, + None, + None, + None, + None, + None, + None, + None + ) + @classmethod def from_document(cls, openpype_version, settings_type, document): document = document or {} @@ -428,6 +444,54 @@ class SettingsHandler: pass + # UI related calls + @abstractmethod + def get_last_opened_info(self): + """Get information about last opened UI. + + Last opened UI is empty if there is noone who would have opened UI at + the moment when called. + + Returns: + Union[None, SettingsStateInfo]: Information about machine who had + opened Settings UI. + """ + + pass + + @abstractmethod + def opened_ui(self): + """Callback called when settings UI is opened. + + Information about this machine must be available when + 'get_last_opened_info' is called from anywhere until 'closed_ui' is + called again. + + Returns: + SettingsStateInfo: Object representing information about this + machine. Must be passed to 'closed_ui' when finished. + """ + + pass + + @abstractmethod + def closed_ui(self, info_obj): + """Callback called when settings UI is closed. + + From the moment this method is called the information about this + machine is removed and no more available when 'get_last_opened_info' + is called. + + Callback should validate if this machine is still stored as opened ui + before changing any value. + + Args: + info_obj (SettingsStateInfo): Object created when 'opened_ui' was + called. + """ + + pass + @six.add_metaclass(ABCMeta) class LocalSettingsHandler: @@ -690,8 +754,7 @@ class MongoSettingsHandler(SettingsHandler): last_saved_info = SettingsStateInfo.create_new( self._current_version, - SYSTEM_SETTINGS_KEY, - None + SYSTEM_SETTINGS_KEY ) self.system_settings_cache.update_last_saved_info( last_saved_info @@ -1610,6 +1673,64 @@ class MongoSettingsHandler(SettingsHandler): return output return self._sort_versions(output) + def get_last_opened_info(self): + doc = self.collection.find_one({ + "type": "last_opened_settings_ui", + "version": self._current_version + }) or {} + info_data = doc.get("info") + if not info_data: + return SettingsStateInfo.create_new_empty(self._current_version) + + # Fill not available information + info_data["openpype_version"] = self._current_version + info_data["settings_type"] = None + info_data["project_name"] = None + return SettingsStateInfo.from_data(info_data) + + def opened_ui(self): + doc_filter = { + "type": "last_opened_settings_ui", + "version": self._current_version + } + + opened_info = SettingsStateInfo.create_new(self._current_version) + new_doc_data = copy.deepcopy(doc_filter) + new_doc_data["info"] = opened_info.to_document_data() + + doc = self.collection.find_one( + doc_filter, + {"_id": True} + ) + if doc: + self.collection.update_one( + {"_id": doc["_id"]}, + {"$set": new_doc_data} + ) + else: + self.collection.insert_one(new_doc_data) + return opened_info + + def closed_ui(self, info_obj): + doc_filter = { + "type": "last_opened_settings_ui", + "version": self._current_version + } + doc = self.collection.find_one(doc_filter) or {} + info_data = doc.get("info") + if not info_data: + return + + info_data["openpype_version"] = self._current_version + info_data["settings_type"] = None + info_data["project_name"] = None + current_info = SettingsStateInfo.from_data(info_data) + if current_info == info_obj: + self.collection.update_one( + {"_id": doc["_id"]}, + {"$set": {"info": None}} + ) + class MongoLocalSettingsHandler(LocalSettingsHandler): """Settings handler that use mongo for store and load local settings. diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 6df41112c8..58cfd3862c 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -91,6 +91,31 @@ def calculate_changes(old_value, new_value): return changes +@require_handler +def get_system_last_saved_info(): + return _SETTINGS_HANDLER.get_system_last_saved_info() + + +@require_handler +def get_project_last_saved_info(project_name): + return _SETTINGS_HANDLER.get_project_last_saved_info(project_name) + + +@require_handler +def get_last_opened_info(): + return _SETTINGS_HANDLER.get_last_opened_info() + + +@require_handler +def opened_ui(): + return _SETTINGS_HANDLER.opened_ui() + + +@require_handler +def closed_ui(info_obj): + return _SETTINGS_HANDLER.closed_ui(info_obj) + + @require_handler def save_studio_settings(data): """Save studio overrides of system settings. From 2b6d705c441fb147945a53d435895e919dc0111b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 18 Aug 2022 17:16:51 +0200 Subject: [PATCH 024/101] Added better logging when DL fails In some specific cases DL sends broken json payload even if response.ok. Handle parsing of broken json better. --- openpype/modules/deadline/abstract_submit_deadline.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 3f54273a56..5cf8222b1c 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -4,6 +4,7 @@ It provides Deadline JobInfo data class. """ +import json.decoder import os from abc import abstractmethod import platform @@ -627,7 +628,12 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug(payload) raise RuntimeError(response.text) - result = response.json() + try: + result = response.json() + except json.decoder.JSONDecodeError: + self.log.warning("Broken response {}".format(response)) + raise RuntimeError("Broken response from DL") + # for submit publish job self._instance.data["deadlineSubmissionJob"] = result From beedfd2ecee833854db505e0566de377a8243649 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 18 Aug 2022 17:22:28 +0200 Subject: [PATCH 025/101] Added better logging when DL fails In some specific cases DL sends broken json payload even if response.ok. Handle parsing of broken json better. --- openpype/modules/deadline/abstract_submit_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 5cf8222b1c..c38f16149e 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -631,7 +631,9 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): try: result = response.json() except json.decoder.JSONDecodeError: - self.log.warning("Broken response {}".format(response)) + msg = "Broken response {}. ".format(response) + msg += "Try restarting DL webservice" + self.log.warning() raise RuntimeError("Broken response from DL") # for submit publish job From 9251a0fd4294725a5fb1dfbef68788c409554230 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 18:09:55 +0200 Subject: [PATCH 026/101] changed function names --- openpype/settings/handlers.py | 20 ++++++++++---------- openpype/settings/lib.py | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 3dc33503ea..1b59531943 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -460,22 +460,22 @@ class SettingsHandler: pass @abstractmethod - def opened_ui(self): + def opened_settings_ui(self): """Callback called when settings UI is opened. Information about this machine must be available when - 'get_last_opened_info' is called from anywhere until 'closed_ui' is - called again. + 'get_last_opened_info' is called from anywhere until + 'closed_settings_ui' is called again. Returns: SettingsStateInfo: Object representing information about this - machine. Must be passed to 'closed_ui' when finished. + machine. Must be passed to 'closed_settings_ui' when finished. """ pass @abstractmethod - def closed_ui(self, info_obj): + def closed_settings_ui(self, info_obj): """Callback called when settings UI is closed. From the moment this method is called the information about this @@ -486,8 +486,8 @@ class SettingsHandler: before changing any value. Args: - info_obj (SettingsStateInfo): Object created when 'opened_ui' was - called. + info_obj (SettingsStateInfo): Object created when + 'opened_settings_ui' was called. """ pass @@ -1680,7 +1680,7 @@ class MongoSettingsHandler(SettingsHandler): }) or {} info_data = doc.get("info") if not info_data: - return SettingsStateInfo.create_new_empty(self._current_version) + return None # Fill not available information info_data["openpype_version"] = self._current_version @@ -1688,7 +1688,7 @@ class MongoSettingsHandler(SettingsHandler): info_data["project_name"] = None return SettingsStateInfo.from_data(info_data) - def opened_ui(self): + def opened_settings_ui(self): doc_filter = { "type": "last_opened_settings_ui", "version": self._current_version @@ -1711,7 +1711,7 @@ class MongoSettingsHandler(SettingsHandler): self.collection.insert_one(new_doc_data) return opened_info - def closed_ui(self, info_obj): + def closed_settings_ui(self, info_obj): doc_filter = { "type": "last_opened_settings_ui", "version": self._current_version diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 58cfd3862c..5eaddf6e6e 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -107,13 +107,13 @@ def get_last_opened_info(): @require_handler -def opened_ui(): - return _SETTINGS_HANDLER.opened_ui() +def opened_settings_ui(): + return _SETTINGS_HANDLER.opened_settings_ui() @require_handler -def closed_ui(info_obj): - return _SETTINGS_HANDLER.closed_ui(info_obj) +def closed_settings_ui(info_obj): + return _SETTINGS_HANDLER.closed_settings_ui(info_obj) @require_handler From baa2505fbfa2055aded2e2c439805d44a3a82347 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 18:10:32 +0200 Subject: [PATCH 027/101] show a dialog if someone else has opened settings UI --- openpype/tools/settings/settings/window.py | 136 ++++++++++++++++++++- 1 file changed, 131 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 22778e4a5b..96f11f3932 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -1,4 +1,16 @@ from Qt import QtWidgets, QtGui, QtCore + +from openpype import style + +from openpype.lib import is_admin_password_required +from openpype.widgets import PasswordDialog + +from openpype.settings.lib import ( + get_last_opened_info, + opened_settings_ui, + closed_settings_ui, +) + from .categories import ( CategoryState, SystemWidget, @@ -10,10 +22,6 @@ from .widgets import ( SettingsTabWidget ) from .search_dialog import SearchEntitiesDialog -from openpype import style - -from openpype.lib import is_admin_password_required -from openpype.widgets import PasswordDialog class MainWidget(QtWidgets.QWidget): @@ -25,6 +33,10 @@ class MainWidget(QtWidgets.QWidget): def __init__(self, user_role, parent=None, reset_on_show=True): super(MainWidget, self).__init__(parent) + # Object referencing to this machine and time when UI was opened + # - is used on close event + self._last_opened_info = None + self._user_passed = False self._reset_on_show = reset_on_show @@ -74,7 +86,7 @@ class MainWidget(QtWidgets.QWidget): self._on_restart_required ) tab_widget.reset_started.connect(self._on_reset_started) - tab_widget.reset_started.connect(self._on_reset_finished) + tab_widget.reset_finished.connect(self._on_reset_finished) tab_widget.full_path_requested.connect(self._on_full_path_request) header_tab_widget.context_menu_requested.connect( @@ -131,11 +143,38 @@ class MainWidget(QtWidgets.QWidget): def showEvent(self, event): super(MainWidget, self).showEvent(event) + if self._reset_on_show: self._reset_on_show = False # Trigger reset with 100ms delay QtCore.QTimer.singleShot(100, self.reset) + elif not self._last_opened_info: + self._check_on_ui_open() + + def _check_on_ui_open(self): + last_opened_info = get_last_opened_info() + if last_opened_info is not None: + if self._last_opened_info != last_opened_info: + self._last_opened_info = None + else: + self._last_opened_info = opened_settings_ui() + + if self._last_opened_info is not None: + return + + dialog = SettingsUIOpenedElsewhere(last_opened_info, self) + dialog.exec_() + if dialog.result() == 1: + self._last_opened_info = opened_settings_ui() + return + + def closeEvent(self, event): + if self._last_opened_info: + closed_settings_ui(self._last_opened_info) + self._last_opened_info = None + super(MainWidget, self).closeEvent(event) + def _show_password_dialog(self): if self._password_dialog: self._password_dialog.open() @@ -221,6 +260,8 @@ class MainWidget(QtWidgets.QWidget): if current_widget is widget: self._update_search_dialog() + self._check_on_ui_open() + def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Find): # todo: search in all widgets (or in active)? @@ -231,3 +272,88 @@ class MainWidget(QtWidgets.QWidget): return return super(MainWidget, self).keyPressEvent(event) + + +class SettingsUIOpenedElsewhere(QtWidgets.QDialog): + def __init__(self, info_obj, parent=None): + super(SettingsUIOpenedElsewhere, self).__init__(parent) + + self._result = 0 + + self.setWindowTitle("Someone else has opened Settings UI") + + message_label = QtWidgets.QLabel(( + "Someone else has opened Settings UI. That may cause data loss." + " Please contact the person on the other side." + "

You can open the UI in view-only mode or take" + " the control which will cause the other settings won't be able" + " to save changes.
" + ), self) + message_label.setWordWrap(True) + + separator_widget_1 = QtWidgets.QFrame(self) + separator_widget_2 = QtWidgets.QFrame(self) + for separator_widget in ( + separator_widget_1, + separator_widget_2 + ): + separator_widget.setObjectName("Separator") + separator_widget.setMinimumHeight(1) + separator_widget.setMaximumHeight(1) + + other_information = QtWidgets.QWidget(self) + other_information_layout = QtWidgets.QFormLayout(other_information) + other_information_layout.setContentsMargins(0, 0, 0, 0) + for label, value in ( + ("Username", info_obj.username), + ("Host name", info_obj.hostname), + ("Host IP", info_obj.hostip), + ("System name", info_obj.system_name), + ("Local ID", info_obj.local_id), + ("Time Stamp", info_obj.timestamp), + ): + other_information_layout.addRow( + label, + QtWidgets.QLabel(value, other_information) + ) + + footer_widget = QtWidgets.QWidget(self) + buttons_widget = QtWidgets.QWidget(footer_widget) + + take_control_btn = QtWidgets.QPushButton( + "Take control", buttons_widget + ) + view_mode_btn = QtWidgets.QPushButton( + "View only", buttons_widget + ) + + buttons_layout = QtWidgets.QHBoxLayout(buttons_widget) + buttons_layout.setContentsMargins(0, 0, 0, 0) + buttons_layout.addWidget(take_control_btn, 1) + buttons_layout.addWidget(view_mode_btn, 1) + + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + footer_layout.setContentsMargins(0, 0, 0, 0) + footer_layout.addStretch(1) + footer_layout.addWidget(buttons_widget, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(message_label, 0) + layout.addWidget(separator_widget_1, 0) + layout.addWidget(other_information, 1, QtCore.Qt.AlignHCenter) + layout.addWidget(separator_widget_2, 0) + layout.addWidget(footer_widget, 0) + + take_control_btn.clicked.connect(self._on_take_control) + view_mode_btn.clicked.connect(self._on_view_mode) + + def result(self): + return self._result + + def _on_take_control(self): + self._result = 1 + self.close() + + def _on_view_mode(self): + self._result = 0 + self.close() From 936363a6608aed3bf4bc4b2a2b6977b9f6d03142 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 18 Aug 2022 18:45:54 +0200 Subject: [PATCH 028/101] settings can take go to view mode or take control --- .../tools/settings/settings/categories.py | 19 ++++++++++ openpype/tools/settings/settings/window.py | 38 +++++++++++++++---- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index f42027d9e2..0410fa1810 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -121,6 +121,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.user_role = user_role self.entity = None + self._edit_mode = None self._state = CategoryState.Idle @@ -191,6 +192,21 @@ class SettingsCategoryWidget(QtWidgets.QWidget): ) raise TypeError("Unknown type: {}".format(label)) + def set_edit_mode(self, enabled): + if enabled is self._edit_mode: + return + + self.save_btn.setEnabled(enabled) + if enabled: + tooltip = ( + "Someone else has opened settings UI." + "\nTry hit refresh to check if settings are already available." + ) + else: + tooltip = "Save settings" + + self.save_btn.setToolTip(tooltip) + @property def state(self): return self._state @@ -434,6 +450,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.set_state(CategoryState.Idle) def save(self): + if not self._edit_mode: + return + if not self.items_are_valid(): return diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 96f11f3932..013a273e98 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -36,6 +36,8 @@ class MainWidget(QtWidgets.QWidget): # Object referencing to this machine and time when UI was opened # - is used on close event self._last_opened_info = None + self._edit_mode = None + self._main_reset = False self._user_passed = False self._reset_on_show = reset_on_show @@ -152,6 +154,12 @@ class MainWidget(QtWidgets.QWidget): elif not self._last_opened_info: self._check_on_ui_open() + def closeEvent(self, event): + if self._last_opened_info: + closed_settings_ui(self._last_opened_info) + self._last_opened_info = None + super(MainWidget, self).closeEvent(event) + def _check_on_ui_open(self): last_opened_info = get_last_opened_info() if last_opened_info is not None: @@ -161,19 +169,27 @@ class MainWidget(QtWidgets.QWidget): self._last_opened_info = opened_settings_ui() if self._last_opened_info is not None: + if self._edit_mode is not True: + self._set_edit_mode(True) + return + + if self._edit_mode is False: return dialog = SettingsUIOpenedElsewhere(last_opened_info, self) dialog.exec_() - if dialog.result() == 1: + edit_enabled = dialog.result() == 1 + if edit_enabled: self._last_opened_info = opened_settings_ui() + self._set_edit_mode(edit_enabled) + + def _set_edit_mode(self, mode): + if self._edit_mode is mode: return - def closeEvent(self, event): - if self._last_opened_info: - closed_settings_ui(self._last_opened_info) - self._last_opened_info = None - super(MainWidget, self).closeEvent(event) + self._edit_mode = mode + for tab_widget in self.tab_widgets: + tab_widget.set_edit_mode(mode) def _show_password_dialog(self): if self._password_dialog: @@ -215,8 +231,11 @@ class MainWidget(QtWidgets.QWidget): if self._reset_on_show: self._reset_on_show = False + self._main_reset = True for tab_widget in self.tab_widgets: tab_widget.reset() + self._main_reset = False + self._check_on_ui_open() def _update_search_dialog(self, clear=False): if self._search_dialog.isVisible(): @@ -260,7 +279,8 @@ class MainWidget(QtWidgets.QWidget): if current_widget is widget: self._update_search_dialog() - self._check_on_ui_open() + if not self._main_reset: + self._check_on_ui_open() def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Find): @@ -340,7 +360,9 @@ class SettingsUIOpenedElsewhere(QtWidgets.QDialog): layout = QtWidgets.QVBoxLayout(self) layout.addWidget(message_label, 0) layout.addWidget(separator_widget_1, 0) - layout.addWidget(other_information, 1, QtCore.Qt.AlignHCenter) + layout.addStretch(1) + layout.addWidget(other_information, 0, QtCore.Qt.AlignHCenter) + layout.addStretch(1) layout.addWidget(separator_widget_2, 0) layout.addWidget(footer_widget, 0) From 407b6b518e0da63e8efd021952c01b6311cf0640 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Aug 2022 12:05:38 +0200 Subject: [PATCH 029/101] use controlled to handle last opened info --- .../tools/settings/settings/categories.py | 23 ++- openpype/tools/settings/settings/window.py | 136 ++++++++++++------ 2 files changed, 114 insertions(+), 45 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 0410fa1810..2e3c6d9dda 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -115,13 +115,19 @@ class SettingsCategoryWidget(QtWidgets.QWidget): "settings to update them to you current running OpenPype version." ) - def __init__(self, user_role, parent=None): + def __init__(self, controller, parent=None): super(SettingsCategoryWidget, self).__init__(parent) - self.user_role = user_role + self._controller = controller + controller.event_system.add_callback( + "edit.mode.changed", + self._edit_mode_changed + ) self.entity = None self._edit_mode = None + self._last_saved_info = None + self._reset_crashed = False self._state = CategoryState.Idle @@ -192,11 +198,16 @@ class SettingsCategoryWidget(QtWidgets.QWidget): ) raise TypeError("Unknown type: {}".format(label)) + def _edit_mode_changed(self, event): + self.set_edit_mode(event["edit_mode"]) + def set_edit_mode(self, enabled): if enabled is self._edit_mode: return - self.save_btn.setEnabled(enabled) + self._edit_mode = enabled + + self.save_btn.setEnabled(enabled and not self._reset_crashed) if enabled: tooltip = ( "Someone else has opened settings UI." @@ -302,7 +313,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): footer_layout = QtWidgets.QHBoxLayout(footer_widget) footer_layout.setContentsMargins(5, 5, 5, 5) - if self.user_role == "developer": + if self._controller.user_role == "developer": self._add_developer_ui(footer_layout, footer_widget) footer_layout.addWidget(empty_label, 1) @@ -683,14 +694,16 @@ class SettingsCategoryWidget(QtWidgets.QWidget): ) def _on_reset_crash(self): + self._reset_crashed = True self.save_btn.setEnabled(False) if self.breadcrumbs_model is not None: self.breadcrumbs_model.set_entity(None) def _on_reset_success(self): + self._reset_crashed = True if not self.save_btn.isEnabled(): - self.save_btn.setEnabled(True) + self.save_btn.setEnabled(self._edit_mode) if self.breadcrumbs_model is not None: path = self.breadcrumbs_bar.path() diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 013a273e98..612975e30a 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -3,6 +3,7 @@ from Qt import QtWidgets, QtGui, QtCore from openpype import style from openpype.lib import is_admin_password_required +from openpype.lib.events import EventSystem from openpype.widgets import PasswordDialog from openpype.settings.lib import ( @@ -24,6 +25,81 @@ from .widgets import ( from .search_dialog import SearchEntitiesDialog +class SettingsController: + """Controller for settings tools. + + Added when tool was finished for checks of last opened in settings + categories and being able communicated with main widget logic. + """ + + def __init__(self, user_role): + self._user_role = user_role + self._event_system = EventSystem() + + self._opened_info = None + self._last_opened_info = None + self._edit_mode = None + + @property + def user_role(self): + return self._user_role + + @property + def event_system(self): + return self._event_system + + @property + def opened_info(self): + return self._opened_info + + @property + def last_opened_info(self): + return self._last_opened_info + + @property + def edit_mode(self): + return self._edit_mode + + def ui_closed(self): + if self._opened_info is not None: + closed_settings_ui(self._opened_info) + + self._opened_info = None + self._edit_mode = None + + def set_edit_mode(self, enabled): + if self._edit_mode is enabled: + return + + opened_info = None + if enabled: + opened_info = opened_settings_ui() + self._last_opened_info = opened_info + + self._opened_info = opened_info + self._edit_mode = enabled + + self.event_system.emit( + "edit.mode.changed", + {"edit_mode": enabled}, + "controller" + ) + + def update_last_opened_info(self): + print("update_last_opened_info") + last_opened_info = get_last_opened_info() + enabled = False + if ( + last_opened_info is None + or self._opened_info == last_opened_info + ): + enabled = True + + self._last_opened_info = last_opened_info + + self.set_edit_mode(enabled) + + class MainWidget(QtWidgets.QWidget): trigger_restart = QtCore.Signal() @@ -33,11 +109,12 @@ class MainWidget(QtWidgets.QWidget): def __init__(self, user_role, parent=None, reset_on_show=True): super(MainWidget, self).__init__(parent) + controller = SettingsController(user_role) + # Object referencing to this machine and time when UI was opened # - is used on close event - self._last_opened_info = None - self._edit_mode = None self._main_reset = False + self._controller = controller self._user_passed = False self._reset_on_show = reset_on_show @@ -55,8 +132,8 @@ class MainWidget(QtWidgets.QWidget): header_tab_widget = SettingsTabWidget(parent=self) - studio_widget = SystemWidget(user_role, header_tab_widget) - project_widget = ProjectWidget(user_role, header_tab_widget) + studio_widget = SystemWidget(controller, header_tab_widget) + project_widget = ProjectWidget(controller, header_tab_widget) tab_widgets = [ studio_widget, @@ -151,45 +228,24 @@ class MainWidget(QtWidgets.QWidget): # Trigger reset with 100ms delay QtCore.QTimer.singleShot(100, self.reset) - elif not self._last_opened_info: - self._check_on_ui_open() - def closeEvent(self, event): - if self._last_opened_info: - closed_settings_ui(self._last_opened_info) - self._last_opened_info = None + self._controller.ui_closed() + super(MainWidget, self).closeEvent(event) - def _check_on_ui_open(self): - last_opened_info = get_last_opened_info() - if last_opened_info is not None: - if self._last_opened_info != last_opened_info: - self._last_opened_info = None - else: - self._last_opened_info = opened_settings_ui() - - if self._last_opened_info is not None: - if self._edit_mode is not True: - self._set_edit_mode(True) + def _check_on_reset(self): + self._controller.update_last_opened_info() + if self._controller.edit_mode: return - if self._edit_mode is False: - return + # if self._edit_mode is False: + # return - dialog = SettingsUIOpenedElsewhere(last_opened_info, self) + dialog = SettingsUIOpenedElsewhere( + self._controller.last_opened_info, self + ) dialog.exec_() - edit_enabled = dialog.result() == 1 - if edit_enabled: - self._last_opened_info = opened_settings_ui() - self._set_edit_mode(edit_enabled) - - def _set_edit_mode(self, mode): - if self._edit_mode is mode: - return - - self._edit_mode = mode - for tab_widget in self.tab_widgets: - tab_widget.set_edit_mode(mode) + self._controller.set_edit_mode(dialog.result() == 1) def _show_password_dialog(self): if self._password_dialog: @@ -235,7 +291,7 @@ class MainWidget(QtWidgets.QWidget): for tab_widget in self.tab_widgets: tab_widget.reset() self._main_reset = False - self._check_on_ui_open() + self._check_on_reset() def _update_search_dialog(self, clear=False): if self._search_dialog.isVisible(): @@ -280,7 +336,7 @@ class MainWidget(QtWidgets.QWidget): self._update_search_dialog() if not self._main_reset: - self._check_on_ui_open() + self._check_on_reset() def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Find): @@ -306,8 +362,8 @@ class SettingsUIOpenedElsewhere(QtWidgets.QDialog): "Someone else has opened Settings UI. That may cause data loss." " Please contact the person on the other side." "

You can open the UI in view-only mode or take" - " the control which will cause the other settings won't be able" - " to save changes.
" + " the control which will cause settings on the other side" + " won't be able to save changes.
" ), self) message_label.setWordWrap(True) From 6d056c774d7011b8810a66bd63073df02596b50c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 19 Aug 2022 12:24:18 +0200 Subject: [PATCH 030/101] Small grammar fixes Just ran through google spellcheck. --- website/docs/dev_settings.md | 77 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/website/docs/dev_settings.md b/website/docs/dev_settings.md index 492d25930d..94590345e8 100644 --- a/website/docs/dev_settings.md +++ b/website/docs/dev_settings.md @@ -4,26 +4,26 @@ title: Settings sidebar_label: Settings --- -Settings gives ability to change how OpenPype behaves in certain situations. Settings are split into 3 categories **system settings**, **project anatomy** and **project settings**. Project anatomy and project settings are in grouped into single category but there is a technical difference (explained later). Only difference in system and project settings is that system settings can't be technically handled on a project level or their values must be available no matter in which project are values received. Settings have headless entities or settings UI. +Settings give the ability to change how OpenPype behaves in certain situations. Settings are split into 3 categories **system settings**, **project anatomy** and **project settings**. Project anatomy and project settings are grouped into a single category but there is a technical difference (explained later). Only difference in system and project settings is that system settings can't be technically handled on a project level or their values must be available no matter in which project the values are received. Settings have headless entities or settings UI. There is one more category **local settings** but they don't have ability to be changed or defined easily. Local settings can change how settings work per machine, can affect both system and project settings but they're hardcoded for predefined values at this moment. ## Settings schemas -System and project settings are defined by settings schemas. Schema define structure of output value, what value types output will contain, how settings are stored and how it's UI input will look. +System and project settings are defined by settings schemas. Schema defines the structure of output value, what value types output will contain, how settings are stored and how its UI input will look. ## Settings values -Output of settings is a json serializable value. There are 3 possible types of value **default values**, **studio overrides** and **project overrides**. Default values must be always available for all settings schemas, their values are stored to code. Default values is what everyone who just installed OpenPype will use as default values. It is good practice to set example values but they should be relevant. +Output of settings is a json serializable value. There are 3 possible types of value **default values**, **studio overrides** and **project overrides**. Default values must be always available for all settings schemas, their values are stored to code. Default values are what everyone who just installed OpenPype will use as default values. It is good practice to set example values but they should be actually relevant. -Setting overrides is what makes settings powerful tool. Overrides contain only a part of settings with additional metadata which describe which parts of settings values that should be replaced from overrides values. Using overrides gives ability to save only specific values and use default values for rest. It is super useful in project settings which have up to 2 levels of overrides. In project settings are used **default values** as base on which are applied **studio overrides** and then **project overrides**. In practice it is possible to save only studio overrides which affect all projects. Changes in studio overrides are then propagated to all projects without project overrides. But values can be locked on project level so studio overrides are not used. +Setting overrides is what makes settings a powerful tool. Overrides contain only a part of settings with additional metadata that describe which parts of settings values should be replaced from overrides values. Using overrides gives the ability to save only specific values and use default values for rest. It is super useful in project settings which have up to 2 levels of overrides. In project settings are used **default values** as base on which are applied **studio overrides** and then **project overrides**. In practice it is possible to save only studio overrides which affect all projects. Changes in studio overrides are then propagated to all projects without project overrides. But values can be locked on project level so studio overrides are not used. ## Settings storage -As was mentined default values are stored into repository files. Overrides are stored to Mongo database. The value in mongo contain only overrides with metadata so their content on it's own is useless and must be used with combination of default values. System settings and project settings are stored into special collection. Single document represents one set of overrides with OpenPype version for which is stored. Settings are versioned and are loaded in specific order - current OpenPype version overrides or first lower available. If there are any overrides with same or lower version then first higher version is used. If there are any overrides then no overrides are applied. +As was mentioned default values are stored into repository files. Overrides are stored in the Mongo database. The value in mongo contain only overrides with metadata so their content on it's own is useless and must be used with combination of default values. System settings and project settings are stored into special collection. Single document represents one set of overrides with OpenPype version for which is stored. Settings are versioned and are loaded in specific order - current OpenPype version overrides or first lower available. If there are any overrides with the same or lower version then the first higher version is used. If there are any overrides then no overrides are applied. -Project anatomy is stored into project document thus is not versioned and it's values are always overriden. Any changes in anatomy schema may have drastic effect on production and OpenPype updates. +Project anatomy is stored into a project document thus is not versioned and its values are always overridden. Any changes in anatomy schema may have a drastic effect on production and OpenPype updates. ## Settings schema items As was mentioned schema items define output type of values, how they are stored and how they look in UI. -- schemas are (by default) defined by a json files +- schemas are (by default) defined by json files - OpenPype core system settings schemas are stored in `~/openpype/settings/entities/schemas/system_schema/` and project settings in `~/openpype/settings/entities/schemas/projects_schema/` - both contain `schema_main.json` which are entry points - OpenPype modules/addons can define their settings schemas using `BaseModuleSettingsDef` in that case some functionality may be slightly modified @@ -31,20 +31,21 @@ As was mentioned schema items define output type of values, how they are stored - **type** is only common key which is required for all schema items - each item may have "input modifiers" (other keys in dictionary) and they may be required or optional based on the type - there are special keys across all items - - `"is_file"` - this key is used when defaults values are stored which define that this key is a filename where it's values are stored - - key is validated must be once in hierarchy else it won't be possible to store default values - - make sense to fill it only if it's value if `true` - - `"is_group"` - define that all values under a key in settings hierarchy will be overridden if any value is modified - - this key is not allowed for all inputs as they may not have technical ability to handle it - - key is validated can be only once in hierarchy and is automatically filled on last possible item if is not defined in schemas - - make sense to fill it only if it's value if `true` + - `"is_file"` - this key is used when defaults values are stored in the file. Its value matches the filename where values are stored + - key is validated, must be unique in hierarchy otherwise it won't be possible to store default values + - make sense to fill it only if it's value if `true` + + - `"is_group"` - define that all values under a key in settings hierarchy will be overridden if any value is modified + - this key is not allowed for all inputs as they may not have technical ability to handle it + - key is validated, must be unique in hierarchy and is automatically filled on last possible item if is not defined in schemas + - make sense to fill it only if it's value if `true` - all entities can have set `"tooltip"` key with description which will be shown in UI on hover ### Inner schema -Settings schemas are big json files which would became unmanageable if would be in single file. To be able to split them into multiple files to help organize them special types `schema` and `template` were added. Both types are relating to a different file by filename. If json file contains dictionary it is considered as `schema` if contains list it is considered as `template`. +Settings schemas are big json files which would become unmanageable if they were in a single file. To be able to split them into multiple files to help organize them special types `schema` and `template` were added. Both types are related to a different file by filename. If a json file contains a dictionary it is considered as `schema` if it contains a list it is considered as a `template`. #### schema -Schema item is replaced by content of entered schema name. It is recommended that schema file is used only once in settings hierarchy. Templates are meant for reusing. +Schema item is replaced by content of entered schema name. It is recommended that the schema file is used only once in settings hierarchy. Templates are meant for reusing. - schema must have `"name"` key which is name of schema that should be used ```javascript @@ -156,7 +157,7 @@ Templates are almost the same as schema items but can contain one or more items } ``` -Template data can be used only to fill templates in values but not in keys. It is also 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). +Template data can be used only to fill templates in values but not in keys. It is also possible to define default values for unfilled fields to do so one of the items in the list must be a dictionary with key "__default_values__"` and value as dictionary with default key: values (as in example above). ```javascript { ... @@ -169,7 +170,7 @@ Template data can be used only to fill templates in values but not in keys. It i } ``` -Because formatting value can be only string it is possible to use formatting values which are replaced with different type. +Because formatting values can be only string it is possible to use formatting values which are replaced with different types. ```javascript // Template data { @@ -201,7 +202,7 @@ Dynamic schema item marks a place in settings schema where schemas defined by `B "name": "project_settings/global" } ``` -- `BaseModuleSettingsDef` with implemented `get_settings_schemas` can return a dictionary where key define a dynamic schema name and value schemas that will be put there +- `BaseModuleSettingsDef` with implemented `get_settings_schemas` can return a dictionary where key defines a dynamic schema name and value schemas that will be put there - dynamic schemas work almost the same way as templates - one item can be replaced by multiple items (or by 0 items) - goal is to dynamically load settings of OpenPype modules without having their schemas or default values in core repository @@ -215,12 +216,12 @@ These inputs wraps another inputs into {key: value} relation #### dict - this is dictionary type wrapping more inputs with keys defined in schema - may be used as dynamic children (e.g. in [list](#list) or [dict-modifiable](#dict-modifiable)) - - in that case the only key modifier is `children` which is list of it's keys - - USAGE: e.g. List of dictionaries where each dictionary have same structure. + - in that case the only key modifier is `children` which is a list of its keys + - USAGE: e.g. List of dictionaries where each dictionary has the same structure. - if is not used as dynamic children then must have defined `"key"` under which are it's values stored - may be with or without `"label"` (only for GUI) - - `"label"` must be set to be able mark item as group with `"is_group"` key set to True -- item with label can visually wrap it's children + - `"label"` must be set to be able to mark item as group with `"is_group"` key set to True +- item with label can visually wrap its children - this option is enabled by default to turn off set `"use_label_wrap"` to `False` - label wrap is by default collapsible - that can be set with key `"collapsible"` to `True`/`False` @@ -314,16 +315,16 @@ These inputs wraps another inputs into {key: value} relation - entity must have defined `"label"` if is not used as widget - is set as group if any parent is not group (can't have children as group) - may be with or without `"label"` (only for GUI) - - `"label"` must be set to be able mark item as group with `"is_group"` key set to True -- item with label can visually wrap it's children - - this option is enabled by default to turn off set `"use_label_wrap"` to `False` - - label wrap is by default collapsible - - that can be set with key `"collapsible"` to `True`/`False` - - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) - - it is possible to add lighter background with `"highlight_content"` (Default: `False`) - - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color -- for UI porposes was added `enum_is_horizontal` which will make combobox appear next to children inputs instead of on top of them (Default: `False`) - - this has extended ability of `enum_on_right` which will move combobox to right side next to children widgets (Default: `False`) + - `"label"` must be set to be able to mark item as group with `"is_group"` key set to True +- item with label can visually wrap its children + - this option is enabled by default to turn off set `"use_label_wrap"` to `False` + - label wrap is by default collapsible + - that can be set with key `"collapsible"` to `True`/`False` + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add lighter background with `"highlight_content"` (Default: `False`) + - lighter background has limits of maximum applies after 3-4 nested highlighted items there is not much difference in the color +- for UI purposes was added `enum_is_horizontal` which will make combobox appear next to children inputs instead of on top of them (Default: `False`) + - this has extended ability of `enum_on_right` which will move combobox to right side next to children widgets (Default: `False`) - output is dictionary `{the "key": children values}` - using this type as template item for list type can be used to create infinite hierarchies @@ -795,7 +796,7 @@ How output of the schema could look like on save: ``` #### color -- preimplemented entity to store and load color values +- pre implemented entity to store and load color values - entity store and expect list of 4 integers in range 0-255 - integers represents rgba [Red, Green, Blue, Alpha] - has modifier `"use_alpha"` which can be `True`/`False` @@ -842,9 +843,9 @@ Items used only for UI purposes. ``` ### Proxy wrappers -- should wraps multiple inputs only visually -- these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled -- can't be used as widget (first item in e.g. `list`, `dict-modifiable`, etc.) +- should wrap multiple inputs only visually +- these do not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled +- can't be used as a widget (first item in e.g. `list`, `dict-modifiable`, etc.) #### form - wraps inputs into form look layout @@ -893,6 +894,6 @@ Items used only for UI purposes. ## How to add new settings -Always start with modifying or adding new schema and don't worry about values. When you think schema is ready to use launch OpenPype settings in development mode using `poetry run python ./start.py settings --dev` or prepared script in `~/openpype/tools/run_settings(.sh|.ps1)`. Settings opened in development mode have checkbox `Modify defaults` available in bottom left corner. When checked default values are modified and saved on `Save`. This is recommended approach how default settings should be created instead of direct modification of files. +Always start with modifying or adding a new schema and don't worry about values. When you think schema is ready to use launch OpenPype settings in development mode using `poetry run python ./start.py settings --dev` or prepared script in `~/openpype/tools/run_settings(.sh|.ps1)`. Settings opened in development mode have the checkbox `Modify defaults` available in the bottom left corner. When checked default values are modified and saved on `Save`. This is a recommended approach on how default settings should be created instead of direct modification of files. ![Modify default settings](assets/settings_dev.png) From 4f03f2dd09f1eed5a0f23d2d9c9a428ae7656560 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Aug 2022 12:33:32 +0200 Subject: [PATCH 031/101] modified dialog --- openpype/tools/settings/settings/window.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 612975e30a..fcbcd129d0 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -359,11 +359,12 @@ class SettingsUIOpenedElsewhere(QtWidgets.QDialog): self.setWindowTitle("Someone else has opened Settings UI") message_label = QtWidgets.QLabel(( - "Someone else has opened Settings UI. That may cause data loss." + "Someone else has opened Settings UI which could cause data loss." " Please contact the person on the other side." - "

You can open the UI in view-only mode or take" - " the control which will cause settings on the other side" - " won't be able to save changes.
" + "

You can open the UI in view-only mode." + " All changes in view mode will be lost." + "

You can take the control which will cause that" + " all changes of settings on the other side will be lost.
" ), self) message_label.setWordWrap(True) @@ -435,3 +436,7 @@ class SettingsUIOpenedElsewhere(QtWidgets.QDialog): def _on_view_mode(self): self._result = 0 self.close() + + def showEvent(self, event): + super(SettingsUIOpenedElsewhere, self).showEvent(event) + self.resize(600, 400) From fc4db8802d260d62a6cd93ea914bb5558151ba0c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 19 Aug 2022 13:01:46 +0200 Subject: [PATCH 032/101] Fixed issues after code review Warning should print exception. JSONDecoder is not in Pype2 --- .../deadline/abstract_submit_deadline.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index c38f16149e..9d952586d2 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -16,7 +16,12 @@ import attr import requests import pyblish.api -from openpype.pipeline.publish import AbstractMetaInstancePlugin +from openpype.pipeline.publish import ( + AbstractMetaInstancePlugin, + KnownPublishError +) + +JSONDecodeError = getattr(json.decoder, "JSONDecodeError", ValueError) def requests_post(*args, **kwargs): @@ -616,7 +621,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): str: resulting Deadline job id. Throws: - RuntimeError: if submission fails. + KnownPublishError: if submission fails. """ url = "{}/api/jobs".format(self._deadline_url) @@ -626,15 +631,15 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): self.log.error(response.status_code) self.log.error(response.content) self.log.debug(payload) - raise RuntimeError(response.text) + raise KnownPublishError(response.text) try: result = response.json() except json.decoder.JSONDecodeError: msg = "Broken response {}. ".format(response) - msg += "Try restarting DL webservice" - self.log.warning() - raise RuntimeError("Broken response from DL") + msg += "Try restarting the Deadline Webservice." + self.log.warning(msg, exc_info=True) + raise KnownPublishError("Broken response from DL") # for submit publish job self._instance.data["deadlineSubmissionJob"] = result From 04397ccd2f8791a54917d505d1453a7a7e7e74cf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 19 Aug 2022 13:10:31 +0200 Subject: [PATCH 033/101] OP-3723 - changed source files to 8K 16K was causing memory issues on some machines. --- openpype/hosts/photoshop/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 64decbb957..60ae575b0a 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -144,7 +144,7 @@ class ExtractReview(openpype.api.Extractor): used as a source for thumbnail or review mov. """ # 16384x16384 actually didn't work because int overflow - max_ffmpeg_size = 16000 + max_ffmpeg_size = 8192 Image.MAX_IMAGE_PIXELS = None first_url = os.path.join(staging_dir, processed_img_names[0]) with Image.open(first_url) as im: From d4bfbe3b9e1510d358f162628170cd29c145a198 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 19 Aug 2022 14:03:06 +0200 Subject: [PATCH 034/101] Updated missed occurence Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/modules/deadline/abstract_submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 9d952586d2..0bad981fdf 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -635,7 +635,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): try: result = response.json() - except json.decoder.JSONDecodeError: + except JSONDecodeError: msg = "Broken response {}. ".format(response) msg += "Try restarting the Deadline Webservice." self.log.warning(msg, exc_info=True) From ba45c7b1694a27005c7f78a47f2e90179bdd11b5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Aug 2022 16:14:41 +0200 Subject: [PATCH 035/101] improving code readability --- openpype/plugins/publish/collect_otio_subset_resources.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 9c19f8a78e..3387cd1176 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -121,10 +121,8 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): otio.schema.ImageSequenceReference ): is_sequence = True - else: - # for OpenTimelineIO 0.12 and older - if metadata.get("padding"): - is_sequence = True + elif metadata.get("padding"): + is_sequence = True self.log.info( "frame_start-frame_end: {}-{}".format(frame_start, frame_end)) From 102965ea69f3ae738dd92af2c39ec9bc8ae577d4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Aug 2022 16:15:16 +0200 Subject: [PATCH 036/101] editorial fixing handles to int and adding speed attribute --- openpype/pipeline/editorial.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/editorial.py b/openpype/pipeline/editorial.py index f62a1842e0..564d78ea6f 100644 --- a/openpype/pipeline/editorial.py +++ b/openpype/pipeline/editorial.py @@ -263,16 +263,17 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): "retime": True, "speed": time_scalar, "timewarps": time_warp_nodes, - "handleStart": round(handle_start), - "handleEnd": round(handle_end) + "handleStart": int(round(handle_start)), + "handleEnd": int(round(handle_end)) } } returning_dict = { "mediaIn": media_in_trimmed, "mediaOut": media_out_trimmed, - "handleStart": round(handle_start), - "handleEnd": round(handle_end) + "handleStart": int(round(handle_start)), + "handleEnd": int(round(handle_end)), + "speed": time_scalar } # add version data only if retime From 869c9255ff266e90ec2f95abae67c234263beefb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Aug 2022 16:15:43 +0200 Subject: [PATCH 037/101] flame: improving extractor of subsets --- .../publish/extract_subset_resources.py | 124 ++++++++++++++++-- 1 file changed, 113 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index d34f5d5854..432bc3b500 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -8,6 +8,9 @@ import pyblish.api import openpype.api from openpype.hosts.flame import api as opfapi from openpype.hosts.flame.api import MediaInfoFile +from openpype.pipeline.editorial import ( + get_media_range_with_retimes +) import flame @@ -65,20 +68,50 @@ class ExtractSubsetResources(openpype.api.Extractor): # get configured workfile frame start/end (handles excluded) frame_start = instance.data["frameStart"] # get media source first frame - source_first_frame = instance.data["sourceFirstFrame"] + source_first_frame = instance.data["sourceFirstFrame"] # 1001 # get timeline in/out of segment clip_in = instance.data["clipIn"] clip_out = instance.data["clipOut"] + # get retimed attributres + retimed_data = self._get_retimed_attributes(instance) + self.log.debug("_ retimed_data: {}".format( + pformat(retimed_data) + )) + # get individual keys + r_handle_start = retimed_data["handle_start"] + r_handle_end = retimed_data["handle_end"] + r_source_dur = retimed_data["source_duration"] + r_speed = retimed_data["speed"] + r_handles = max(r_handle_start, r_handle_end) + # get handles value - take only the max from both handle_start = instance.data["handleStart"] - handle_end = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] handles = max(handle_start, handle_end) + include_handles = instance.data.get("includeHandles") + self.log.debug("_ include_handles: {}".format(include_handles)) # get media source range with handles source_start_handles = instance.data["sourceStartH"] source_end_handles = instance.data["sourceEndH"] + # retime if needed + if r_speed != 1.0: + source_start_handles = ( + instance.data["sourceStart"] - r_handle_start) + source_end_handles = ( + source_start_handles + # TODO: duration exclude 1 - might be problem + + (r_source_dur - 1) + + r_handle_start + + r_handle_end + ) + + self.log.debug("_ source_start_handles: {}".format( + source_start_handles)) + self.log.debug("_ source_end_handles: {}".format( + source_end_handles)) # create staging dir path staging_dir = self.staging_dir(instance) @@ -93,6 +126,19 @@ class ExtractSubsetResources(openpype.api.Extractor): } export_presets.update(self.export_presets_mapping) + # set versiondata if any retime + version_data = retimed_data.get("version_data") + + if version_data: + instance.data["versionData"].update(version_data) + + if instance.data.get("versionData"): + if r_speed != 1.0: + instance.data["versionData"].update({ + "frameStart": source_start_handles + r_handle_start, + "frameEnd": source_end_handles - r_handle_end, + }) + # loop all preset names and for unique_name, preset_config in export_presets.items(): modify_xml_data = {} @@ -117,14 +163,22 @@ class ExtractSubsetResources(openpype.api.Extractor): # get frame range with handles for representation range frame_start_handle = frame_start - handle_start + if include_handles: + if r_speed == 1.0: + frame_start_handle = frame_start + else: + frame_start_handle = ( + frame_start - handle_start) + r_handle_start + + self.log.debug("_ frame_start_handle: {}".format( + frame_start_handle)) # calculate duration with handles source_duration_handles = ( - source_end_handles - source_start_handles) + source_end_handles - source_start_handles) + 1 - # define in/out marks - in_mark = (source_start_handles - source_first_frame) + 1 - out_mark = in_mark + source_duration_handles + self.log.debug("_ source_duration_handles: {}".format( + source_duration_handles)) exporting_clip = None name_patern_xml = "_{}.".format( @@ -142,19 +196,28 @@ class ExtractSubsetResources(openpype.api.Extractor): "__{}.").format( unique_name) - # change in/out marks to timeline in/out + # only for h264 with baked retime in_mark = clip_in - out_mark = clip_out + out_mark = clip_out + 1 + + modify_xml_data["nbHandles"] = handles else: + in_mark = (source_start_handles - source_first_frame) + 1 + out_mark = in_mark + source_duration_handles exporting_clip = self.import_clip(clip_path) exporting_clip.name.set_value("{}_{}".format( asset_name, segment_name)) + modify_xml_data["nbHandles"] = ( + handles if r_speed == 1.0 else r_handles) # add xml tags modifications modify_xml_data.update({ + # TODO: handles only to Sequence preset + # TODO: enable Start frame attribute "exportHandles": True, - "nbHandles": handles, - "startFrame": frame_start, + "startFrame": frame_start_handle, + # enum position low start from 0 + "frameIndex": 0, "namePattern": name_patern_xml }) @@ -162,6 +225,12 @@ class ExtractSubsetResources(openpype.api.Extractor): # add any xml overrides collected form segment.comment modify_xml_data.update(instance.data["xml_overrides"]) + self.log.debug(pformat(modify_xml_data)) + self.log.debug("_ sequence publish {}".format( + export_type == "Sequence Publish")) + self.log.debug("_ in_mark: {}".format(in_mark)) + self.log.debug("_ out_mark: {}".format(out_mark)) + export_kwargs = {} # validate xml preset file is filled if preset_file == "": @@ -283,7 +352,7 @@ class ExtractSubsetResources(openpype.api.Extractor): representation_data.update({ "frameStart": frame_start_handle, "frameEnd": ( - frame_start_handle + source_duration_handles), + frame_start_handle + source_duration_handles) - 1, "fps": instance.data["fps"] }) @@ -303,6 +372,39 @@ class ExtractSubsetResources(openpype.api.Extractor): self.log.debug("All representations: {}".format( pformat(instance.data["representations"]))) + def _get_retimed_attributes(self, instance): + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + include_handles = instance.data.get("includeHandles") + self.log.debug("_ include_handles: {}".format(include_handles)) + + # get basic variables + otio_clip = instance.data["otioClip"] + otio_avalable_range = otio_clip.available_range() + available_duration = otio_avalable_range.duration.value + self.log.debug( + ">> available_duration: {}".format(available_duration)) + + # get available range trimmed with processed retimes + retimed_attributes = get_media_range_with_retimes( + otio_clip, handle_start, handle_end) + self.log.debug( + ">> retimed_attributes: {}".format(retimed_attributes)) + + r_media_in = int(retimed_attributes["mediaIn"]) + r_media_out = int(retimed_attributes["mediaOut"]) + version_data = retimed_attributes.get("versionData") + + return { + "version_data": version_data, + "handle_start": int(retimed_attributes["handleStart"]), + "handle_end": int(retimed_attributes["handleEnd"]), + "source_duration": ( + (r_media_out - r_media_in) + 1 + ), + "speed": float(retimed_attributes["speed"]) + } + def _should_skip(self, preset_config, clip_path, unique_name): # get activating attributes activated_preset = preset_config["active"] From faec36f1d65292121af9be129b2857d7d100a60f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Aug 2022 16:34:37 +0200 Subject: [PATCH 038/101] code cleanup --- .../plugins/publish/extract_subset_resources.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 432bc3b500..2f4f90fe55 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -84,7 +84,6 @@ class ExtractSubsetResources(openpype.api.Extractor): r_handle_end = retimed_data["handle_end"] r_source_dur = retimed_data["source_duration"] r_speed = retimed_data["speed"] - r_handles = max(r_handle_start, r_handle_end) # get handles value - take only the max from both handle_start = instance.data["handleStart"] @@ -183,6 +182,7 @@ class ExtractSubsetResources(openpype.api.Extractor): exporting_clip = None name_patern_xml = "_{}.".format( unique_name) + if export_type == "Sequence Publish": # change export clip to sequence exporting_clip = flame.duplicate(sequence_clip) @@ -199,25 +199,22 @@ class ExtractSubsetResources(openpype.api.Extractor): # only for h264 with baked retime in_mark = clip_in out_mark = clip_out + 1 - - modify_xml_data["nbHandles"] = handles + modify_xml_data.update({ + "exportHandles": True, + "nbHandles": handles + }) else: in_mark = (source_start_handles - source_first_frame) + 1 out_mark = in_mark + source_duration_handles exporting_clip = self.import_clip(clip_path) exporting_clip.name.set_value("{}_{}".format( asset_name, segment_name)) - modify_xml_data["nbHandles"] = ( - handles if r_speed == 1.0 else r_handles) # add xml tags modifications modify_xml_data.update({ - # TODO: handles only to Sequence preset - # TODO: enable Start frame attribute - "exportHandles": True, - "startFrame": frame_start_handle, # enum position low start from 0 "frameIndex": 0, + "startFrame": frame_start_handle, "namePattern": name_patern_xml }) From 8d08d5966a5eb213d4a8de57bf497cda83ccb631 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Aug 2022 17:39:59 +0200 Subject: [PATCH 039/101] cleaning code --- .../publish/extract_subset_resources.py | 48 ++++--------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 2f4f90fe55..ddf126c445 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -50,7 +50,6 @@ class ExtractSubsetResources(openpype.api.Extractor): export_presets_mapping = {} def process(self, instance): - if not self.keep_original_representation: # remove previeous representation if not needed instance.data["representations"] = [] @@ -68,7 +67,7 @@ class ExtractSubsetResources(openpype.api.Extractor): # get configured workfile frame start/end (handles excluded) frame_start = instance.data["frameStart"] # get media source first frame - source_first_frame = instance.data["sourceFirstFrame"] # 1001 + source_first_frame = instance.data["sourceFirstFrame"] # get timeline in/out of segment clip_in = instance.data["clipIn"] @@ -76,9 +75,7 @@ class ExtractSubsetResources(openpype.api.Extractor): # get retimed attributres retimed_data = self._get_retimed_attributes(instance) - self.log.debug("_ retimed_data: {}".format( - pformat(retimed_data) - )) + # get individual keys r_handle_start = retimed_data["handle_start"] r_handle_end = retimed_data["handle_end"] @@ -90,7 +87,6 @@ class ExtractSubsetResources(openpype.api.Extractor): handle_end = instance.data["handleEnd"] handles = max(handle_start, handle_end) include_handles = instance.data.get("includeHandles") - self.log.debug("_ include_handles: {}".format(include_handles)) # get media source range with handles source_start_handles = instance.data["sourceStartH"] @@ -101,17 +97,11 @@ class ExtractSubsetResources(openpype.api.Extractor): instance.data["sourceStart"] - r_handle_start) source_end_handles = ( source_start_handles - # TODO: duration exclude 1 - might be problem + (r_source_dur - 1) + r_handle_start + r_handle_end ) - self.log.debug("_ source_start_handles: {}".format( - source_start_handles)) - self.log.debug("_ source_end_handles: {}".format( - source_end_handles)) - # create staging dir path staging_dir = self.staging_dir(instance) @@ -125,18 +115,20 @@ class ExtractSubsetResources(openpype.api.Extractor): } export_presets.update(self.export_presets_mapping) + if not instance.data.get("versionData"): + instance.data["versionData"] = {} + # set versiondata if any retime version_data = retimed_data.get("version_data") if version_data: instance.data["versionData"].update(version_data) - if instance.data.get("versionData"): - if r_speed != 1.0: - instance.data["versionData"].update({ - "frameStart": source_start_handles + r_handle_start, - "frameEnd": source_end_handles - r_handle_end, - }) + if r_speed != 1.0: + instance.data["versionData"].update({ + "frameStart": source_start_handles + r_handle_start, + "frameEnd": source_end_handles - r_handle_end, + }) # loop all preset names and for unique_name, preset_config in export_presets.items(): @@ -176,9 +168,6 @@ class ExtractSubsetResources(openpype.api.Extractor): source_duration_handles = ( source_end_handles - source_start_handles) + 1 - self.log.debug("_ source_duration_handles: {}".format( - source_duration_handles)) - exporting_clip = None name_patern_xml = "_{}.".format( unique_name) @@ -222,9 +211,6 @@ class ExtractSubsetResources(openpype.api.Extractor): # add any xml overrides collected form segment.comment modify_xml_data.update(instance.data["xml_overrides"]) - self.log.debug(pformat(modify_xml_data)) - self.log.debug("_ sequence publish {}".format( - export_type == "Sequence Publish")) self.log.debug("_ in_mark: {}".format(in_mark)) self.log.debug("_ out_mark: {}".format(out_mark)) @@ -264,7 +250,6 @@ class ExtractSubsetResources(openpype.api.Extractor): thumb_frame_number = int(in_mark + ( source_duration_handles / 2)) - self.log.debug("__ in_mark: {}".format(in_mark)) self.log.debug("__ thumb_frame_number: {}".format( thumb_frame_number )) @@ -276,9 +261,6 @@ class ExtractSubsetResources(openpype.api.Extractor): "out_mark": out_mark }) - self.log.debug("__ modify_xml_data: {}".format( - pformat(modify_xml_data) - )) preset_path = opfapi.modify_preset_file( preset_orig_xml_path, staging_dir, modify_xml_data) @@ -366,21 +348,13 @@ class ExtractSubsetResources(openpype.api.Extractor): # at the end remove the duplicated clip flame.delete(exporting_clip) - self.log.debug("All representations: {}".format( - pformat(instance.data["representations"]))) def _get_retimed_attributes(self, instance): handle_start = instance.data["handleStart"] handle_end = instance.data["handleEnd"] - include_handles = instance.data.get("includeHandles") - self.log.debug("_ include_handles: {}".format(include_handles)) # get basic variables otio_clip = instance.data["otioClip"] - otio_avalable_range = otio_clip.available_range() - available_duration = otio_avalable_range.duration.value - self.log.debug( - ">> available_duration: {}".format(available_duration)) # get available range trimmed with processed retimes retimed_attributes = get_media_range_with_retimes( @@ -412,8 +386,6 @@ class ExtractSubsetResources(openpype.api.Extractor): unique_name, activated_preset, filter_path_regex ) ) - self.log.debug( - "__ clip_path: `{}`".format(clip_path)) # skip if not activated presete if not activated_preset: From b15471501a44a09e5e205460aea6b6fc0c365e91 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 19 Aug 2022 17:41:11 +0200 Subject: [PATCH 040/101] hound suggestions --- openpype/hosts/flame/api/lib.py | 1 - openpype/hosts/flame/plugins/publish/extract_subset_resources.py | 1 - 2 files changed, 2 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index a5ae3c4468..94c46fe937 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -1,7 +1,6 @@ import sys import os import re -import sys import json import pickle import clique diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index ddf126c445..8a03ba119c 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -348,7 +348,6 @@ class ExtractSubsetResources(openpype.api.Extractor): # at the end remove the duplicated clip flame.delete(exporting_clip) - def _get_retimed_attributes(self, instance): handle_start = instance.data["handleStart"] handle_end = instance.data["handleEnd"] From 5d14fdd1cef6eb2d40925efeadfbcab3af219ca5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Aug 2022 19:02:10 +0200 Subject: [PATCH 041/101] fix _reset_crashed --- openpype/tools/settings/settings/categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 2e3c6d9dda..fd95b4ca71 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -701,7 +701,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.breadcrumbs_model.set_entity(None) def _on_reset_success(self): - self._reset_crashed = True + self._reset_crashed = False if not self.save_btn.isEnabled(): self.save_btn.setEnabled(self._edit_mode) From 890d1becaa8a4fcc597977d6b0cbe25e21bf34d3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 19 Aug 2022 19:11:00 +0200 Subject: [PATCH 042/101] moved dialog to separated file --- openpype/tools/settings/settings/dialogs.py | 115 ++++++++++++++++++++ openpype/tools/settings/settings/window.py | 93 +--------------- 2 files changed, 116 insertions(+), 92 deletions(-) create mode 100644 openpype/tools/settings/settings/dialogs.py diff --git a/openpype/tools/settings/settings/dialogs.py b/openpype/tools/settings/settings/dialogs.py new file mode 100644 index 0000000000..dea056b89d --- /dev/null +++ b/openpype/tools/settings/settings/dialogs.py @@ -0,0 +1,115 @@ +from Qt import QtWidgets, QtCore + + +class BaseInfoDialog(QtWidgets.QDialog): + width = 600 + height = 400 + + def __init__(self, message, title, info_obj, parent=None): + super(BaseInfoDialog, self).__init__(parent) + self._result = 0 + self._info_obj = info_obj + + self.setWindowTitle(title) + + message_label = QtWidgets.QLabel(message, self) + message_label.setWordWrap(True) + + separator_widget_1 = QtWidgets.QFrame(self) + separator_widget_2 = QtWidgets.QFrame(self) + for separator_widget in ( + separator_widget_1, + separator_widget_2 + ): + separator_widget.setObjectName("Separator") + separator_widget.setMinimumHeight(1) + separator_widget.setMaximumHeight(1) + + other_information = QtWidgets.QWidget(self) + other_information_layout = QtWidgets.QFormLayout(other_information) + other_information_layout.setContentsMargins(0, 0, 0, 0) + for label, value in ( + ("Username", info_obj.username), + ("Host name", info_obj.hostname), + ("Host IP", info_obj.hostip), + ("System name", info_obj.system_name), + ("Local ID", info_obj.local_id), + ("Time Stamp", info_obj.timestamp), + ): + other_information_layout.addRow( + label, + QtWidgets.QLabel(value, other_information) + ) + + footer_widget = QtWidgets.QWidget(self) + buttons_widget = QtWidgets.QWidget(footer_widget) + + buttons_layout = QtWidgets.QHBoxLayout(buttons_widget) + buttons_layout.setContentsMargins(0, 0, 0, 0) + buttons = self.get_buttons(buttons_widget) + for button in buttons: + buttons_layout.addWidget(button, 1) + + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + footer_layout.setContentsMargins(0, 0, 0, 0) + footer_layout.addStretch(1) + footer_layout.addWidget(buttons_widget, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(message_label, 0) + layout.addWidget(separator_widget_1, 0) + layout.addStretch(1) + layout.addWidget(other_information, 0, QtCore.Qt.AlignHCenter) + layout.addStretch(1) + layout.addWidget(separator_widget_2, 0) + layout.addWidget(footer_widget, 0) + + def showEvent(self, event): + super(BaseInfoDialog, self).showEvent(event) + self.resize(self.width, self.height) + + def result(self): + return self._result + + def get_buttons(self, parent): + return [] + + +class SettingsUIOpenedElsewhere(BaseInfoDialog): + def __init__(self, info_obj, parent=None): + title = "Someone else has opened Settings UI" + message = ( + "Someone else has opened Settings UI which could cause data loss." + " Please contact the person on the other side." + "

You can continue in view-only mode." + " All changes in view mode will be lost." + "

You can take control which will cause that" + " all changes of settings on the other side will be lost.
" + ) + super(SettingsUIOpenedElsewhere, self).__init__( + message, title, info_obj, parent + ) + + def _on_take_control(self): + self._result = 1 + self.close() + + def _on_view_mode(self): + self._result = 0 + self.close() + + def get_buttons(self, parent): + take_control_btn = QtWidgets.QPushButton( + "Take control", parent + ) + view_mode_btn = QtWidgets.QPushButton( + "View only", parent + ) + + take_control_btn.clicked.connect(self._on_take_control) + view_mode_btn.clicked.connect(self._on_view_mode) + + return [ + take_control_btn, + view_mode_btn + ] diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index fcbcd129d0..2750785535 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -12,6 +12,7 @@ from openpype.settings.lib import ( closed_settings_ui, ) +from .dialogs import SettingsUIOpenedElsewhere from .categories import ( CategoryState, SystemWidget, @@ -348,95 +349,3 @@ class MainWidget(QtWidgets.QWidget): return return super(MainWidget, self).keyPressEvent(event) - - -class SettingsUIOpenedElsewhere(QtWidgets.QDialog): - def __init__(self, info_obj, parent=None): - super(SettingsUIOpenedElsewhere, self).__init__(parent) - - self._result = 0 - - self.setWindowTitle("Someone else has opened Settings UI") - - message_label = QtWidgets.QLabel(( - "Someone else has opened Settings UI which could cause data loss." - " Please contact the person on the other side." - "

You can open the UI in view-only mode." - " All changes in view mode will be lost." - "

You can take the control which will cause that" - " all changes of settings on the other side will be lost.
" - ), self) - message_label.setWordWrap(True) - - separator_widget_1 = QtWidgets.QFrame(self) - separator_widget_2 = QtWidgets.QFrame(self) - for separator_widget in ( - separator_widget_1, - separator_widget_2 - ): - separator_widget.setObjectName("Separator") - separator_widget.setMinimumHeight(1) - separator_widget.setMaximumHeight(1) - - other_information = QtWidgets.QWidget(self) - other_information_layout = QtWidgets.QFormLayout(other_information) - other_information_layout.setContentsMargins(0, 0, 0, 0) - for label, value in ( - ("Username", info_obj.username), - ("Host name", info_obj.hostname), - ("Host IP", info_obj.hostip), - ("System name", info_obj.system_name), - ("Local ID", info_obj.local_id), - ("Time Stamp", info_obj.timestamp), - ): - other_information_layout.addRow( - label, - QtWidgets.QLabel(value, other_information) - ) - - footer_widget = QtWidgets.QWidget(self) - buttons_widget = QtWidgets.QWidget(footer_widget) - - take_control_btn = QtWidgets.QPushButton( - "Take control", buttons_widget - ) - view_mode_btn = QtWidgets.QPushButton( - "View only", buttons_widget - ) - - buttons_layout = QtWidgets.QHBoxLayout(buttons_widget) - buttons_layout.setContentsMargins(0, 0, 0, 0) - buttons_layout.addWidget(take_control_btn, 1) - buttons_layout.addWidget(view_mode_btn, 1) - - footer_layout = QtWidgets.QHBoxLayout(footer_widget) - footer_layout.setContentsMargins(0, 0, 0, 0) - footer_layout.addStretch(1) - footer_layout.addWidget(buttons_widget, 0) - - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(message_label, 0) - layout.addWidget(separator_widget_1, 0) - layout.addStretch(1) - layout.addWidget(other_information, 0, QtCore.Qt.AlignHCenter) - layout.addStretch(1) - layout.addWidget(separator_widget_2, 0) - layout.addWidget(footer_widget, 0) - - take_control_btn.clicked.connect(self._on_take_control) - view_mode_btn.clicked.connect(self._on_view_mode) - - def result(self): - return self._result - - def _on_take_control(self): - self._result = 1 - self.close() - - def _on_view_mode(self): - self._result = 0 - self.close() - - def showEvent(self, event): - super(SettingsUIOpenedElsewhere, self).showEvent(event) - self.resize(600, 400) From 2ea277992ea217ff9d4b65cd3b36dd5ad2218f8e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 19 Aug 2022 21:06:05 +0200 Subject: [PATCH 043/101] update ftrack cli commands --- website/docs/module_ftrack.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index 667782754f..ad9cf75e8f 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -13,7 +13,7 @@ Ftrack is currently the main project management option for OpenPype. This docume ## Prepare Ftrack for OpenPype ### Server URL -If you want to connect Ftrack to OpenPype you might need to make few changes in Ftrack settings. These changes would take a long time to do manually, so we prepared a few Ftrack actions to help you out. First, you'll need to launch OpenPype settings, enable [Ftrack module](admin_settings_system.md#Ftrack), and enter the address to your Ftrack server. +If you want to connect Ftrack to OpenPype you might need to make few changes in Ftrack settings. These changes would take a long time to do manually, so we prepared a few Ftrack actions to help you out. First, you'll need to launch OpenPype settings, enable [Ftrack module](admin_settings_system.md#Ftrack), and enter the address to your Ftrack server. ### Login Once your server is configured, restart OpenPype and you should be prompted to enter your [Ftrack credentials](artist_ftrack.md#How-to-use-Ftrack-in-OpenPype) to be able to run our Ftrack actions. If you are already logged in to Ftrack in your browser, it is enough to press `Ftrack login` and it will connect automatically. @@ -26,7 +26,7 @@ You can only use our Ftrack Actions and publish to Ftrack if each artist is logg ### Custom Attributes After successfully connecting OpenPype with you Ftrack, you can right click on any project in Ftrack and you should see a bunch of actions available. The most important one is called `OpenPype Admin` and contains multiple options inside. -To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Custom Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. +To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Create/Update Custom Attributes](manager_ftrack_actions.md#create-update-avalon-attributes), which creates and sets the Custom Attributes necessary for OpenPype to function. @@ -34,7 +34,7 @@ To prepare Ftrack for working with OpenPype you'll need to run [OpenPype Admin - Ftrack Event Server is the key to automation of many tasks like _status change_, _thumbnail update_, _automatic synchronization to Avalon database_ and many more. Event server should run at all times to perform the required processing as it is not possible to catch some of them retrospectively with enough certainty. ### Running event server -There are specific launch arguments for event server. With `openpype_console eventserver` you can launch event server but without prior preparation it will terminate immediately. The reason is that event server requires 3 pieces of information: _Ftrack server url_, _paths to events_ and _credentials (Username and API key)_. Ftrack server URL and Event path are set from OpenPype's environments by default, but the credentials must be done separatelly for security reasons. +There are specific launch arguments for event server. With `openpype_console module ftrack eventserver` you can launch event server but without prior preparation it will terminate immediately. The reason is that event server requires 3 pieces of information: _Ftrack server url_, _paths to events_ and _credentials (Username and API key)_. Ftrack server URL and Event path are set from OpenPype's environments by default, but the credentials must be done separatelly for security reasons. @@ -53,7 +53,7 @@ There are specific launch arguments for event server. With `openpype_console eve - **`--ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee"`** : User's API key - `--ftrack-url "https://yourdomain.ftrackapp.com/"` : Ftrack server URL _(it is not needed to enter if you have set `FTRACK_SERVER` in OpenPype' environments)_ -So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `openpype_console.exe eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `openpype_console.exe eventserver`. +So if you want to use OpenPype's environments then you can launch event server for first time with these arguments `openpype_console.exe module ftrack eventserver --ftrack-user "my.username" --ftrack-api-key "00000aaa-11bb-22cc-33dd-444444eeeee" --store-credentials`. Since that time, if everything was entered correctly, you can launch event server with `openpype_console.exe module ftrack eventserver`. @@ -72,7 +72,7 @@ We do not recommend setting your Ftrack user and api key environments in a persi ### Where to run event server -We recommend you to run event server on stable server machine with ability to connect to Avalon database and Ftrack web server. Best practice we recommend is to run event server as service. It can be Windows or Linux. +We recommend you to run event server on stable server machine with ability to connect to Avalon database and Ftrack web server. Best practice we recommend is to run event server as service. It can be Windows or Linux. :::important Event server should **not** run more than once! It may cause major issues. @@ -99,11 +99,10 @@ Event server should **not** run more than once! It may cause major issues. - add content to the file: ```sh #!/usr/bin/env bash -export OPENPYPE_DEBUG=1 export OPENPYPE_MONGO= pushd /mnt/path/to/openpype -./openpype_console eventserver --ftrack-user --ftrack-api-key +./openpype_console module ftrack eventserver --ftrack-user --ftrack-api-key --debug ``` - change file permission: `sudo chmod 0755 /opt/openpype/run_event_server.sh` @@ -140,14 +139,13 @@ WantedBy=multi-user.target - create service file: `openpype-ftrack-eventserver.bat` -- add content to the service file: +- add content to the service file: ```sh @echo off -set OPENPYPE_DEBUG=1 set OPENPYPE_MONGO= pushd \\path\to\openpype -openpype_console.exe eventserver --ftrack-user --ftrack-api-key +openpype_console.exe module ftrack eventserver --ftrack-user --ftrack-api-key --debug ``` - download and install `nssm.cc` - create Windows service according to nssm.cc manual @@ -174,7 +172,7 @@ This event updates entities on their changes Ftrack. When new entity is created Deleting an entity by Ftrack's default is not processed for security reasons _(to delete entity use [Delete Asset/Subset action](manager_ftrack_actions.md#delete-asset-subset))_. ::: -### Synchronize Hierarchical and Entity Attributes +### Synchronize Hierarchical and Entity Attributes Auto-synchronization of hierarchical attributes from Ftrack entities. @@ -190,7 +188,7 @@ Change status of next task from `Not started` to `Ready` when previous task is a Multiple detailed rules for next task update can be configured in the settings. -### Delete Avalon ID from new entity +### Delete Avalon ID from new entity Is used to remove value from `Avalon/Mongo Id` Custom Attribute when entity is created. @@ -215,7 +213,7 @@ This event handler allows setting of different status to a first created Asset V This is useful for example if first version publish doesn't contain any actual reviewable work, but is only used for roundtrip conform check, in which case this version could receive status `pending conform` instead of standard `pending review` ### Update status on next task -Change status on next task by task types order when task status state changed to "Done". All tasks with the same Task mapping of next task status changes From → To. Some status can be ignored. +Change status on next task by task types order when task status state changed to "Done". All tasks with the same Task mapping of next task status changes From → To. Some status can be ignored. ## Publish plugins @@ -238,7 +236,7 @@ Add Ftrack Family: enabled #### Advanced adding if additional families present -In special cases adding 'ftrack' based on main family ('Families' set higher) is not enough. +In special cases adding 'ftrack' based on main family ('Families' set higher) is not enough. (For example upload to Ftrack for 'plate' main family should only happen if 'review' is contained in instance 'families', not added in other cases. ) -![Collect Ftrack Family](assets/ftrack/ftrack-collect-advanced.png) \ No newline at end of file +![Collect Ftrack Family](assets/ftrack/ftrack-collect-advanced.png) From 752f1cde1c307398fa56ada929f8299ee97face5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 19 Aug 2022 21:10:28 +0200 Subject: [PATCH 044/101] removed outdated repositories information --- website/docs/system_introduction.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/website/docs/system_introduction.md b/website/docs/system_introduction.md index 71c5d64aa8..db297b527a 100644 --- a/website/docs/system_introduction.md +++ b/website/docs/system_introduction.md @@ -48,24 +48,3 @@ to the table - Some DCCs do not support using Environment variables in file paths. This will make it very hard to maintain full multiplatform compatibility as well variable storage roots. - Relying on VPN connection and using it to work directly of network storage will be painfully slow. - - -## Repositories - -### [OpenPype](https://github.com/pypeclub/pype) - -This is where vast majority of the code that works with your data lives. It acts -as Avalon-Config, if we're speaking in avalon terms. - -Avalon gives us the ability to work with a certain host, say Maya, in a standardized manner, but OpenPype defines **how** we work with all the data, allows most of the behavior to be configured on a very granular level and provides a comprehensive build and installation tools for it. - -Thanks to that, we are able to maintain one codebase for vast majority of the features across all our clients deployments while keeping the option to tailor the pipeline to each individual studio. - -### [Avalon-core](https://github.com/pypeclub/avalon-core) - -Avalon-core is the heart of OpenPype. It provides the base functionality including key GUIs (albeit expanded and modified by us), database connection, standards for data structures, working with entities and some universal tools. - -Avalon is being actively developed and maintained by a community of studios and TDs from around the world, with Pype Club team being an active contributor as well. - -Due to the extensive work we've done on OpenPype and the need to react quickly to production needs, we -maintain our own fork of avalon-core, which is kept up to date with upstream changes as much as possible. From 40efddb9b40a3d74d136dd9a06647fd2b73ded91 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 19 Aug 2022 21:21:28 +0200 Subject: [PATCH 045/101] removed eventserver from global cli commands --- website/docs/admin_openpype_commands.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md index 53fc12410f..cf45138fa9 100644 --- a/website/docs/admin_openpype_commands.md +++ b/website/docs/admin_openpype_commands.md @@ -40,7 +40,6 @@ For more information [see here](admin_use.md#run-openpype). | module | Run command line arguments for modules. | | | repack-version | Tool to re-create version zip. | [📑](#repack-version-arguments) | | tray | Launch OpenPype Tray. | [📑](#tray-arguments) -| eventserver | This should be ideally used by system service (such as systemd or upstart on linux and window service). | [📑](#eventserver-arguments) | | launch | Launch application in Pype environment. | [📑](#launch-arguments) | | publish | Pype takes JSON from provided path and use it to publish data in it. | [📑](#publish-arguments) | | extractenvironments | Extract environment variables for entered context to a json file. | [📑](#extractenvironments-arguments) | @@ -57,25 +56,7 @@ For more information [see here](admin_use.md#run-openpype). openpype_console tray ``` --- -### `launch` arguments {#eventserver-arguments} -You have to set either proper environment variables to provide URL and credentials or use -option to specify them. -| Argument | Description | -| --- | --- | -| `--ftrack-url` | URL to ftrack server (can be set with `FTRACK_SERVER`) | -| `--ftrack-user` |user name to log in to ftrack (can be set with `FTRACK_API_USER`) | -| `--ftrack-api-key` | ftrack api key (can be set with `FTRACK_API_KEY`) | -| `--legacy` | run event server without mongo storing | -| `--clockify-api-key` | Clockify API key (can be set with `CLOCKIFY_API_KEY`) | -| `--clockify-workspace` | Clockify workspace (can be set with `CLOCKIFY_WORKSPACE`) | - -To run ftrack event server: -```shell -openpype_console eventserver --ftrack-url= --ftrack-user= --ftrack-api-key= -``` - ---- ### `launch` arguments {#launch-arguments} | Argument | Description | From ce4c00d20a677c08bcd046ec24a0a3ba6c9e4792 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 19 Aug 2022 21:46:21 +0200 Subject: [PATCH 046/101] add information about naming limitations to key concepts --- website/docs/artist_concepts.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/docs/artist_concepts.md b/website/docs/artist_concepts.md index 9005cffe87..f67ab89b9c 100644 --- a/website/docs/artist_concepts.md +++ b/website/docs/artist_concepts.md @@ -10,6 +10,8 @@ sidebar_label: Key Concepts In our pipeline all the main entities the project is made from are internally considered *'Assets'*. Episode, sequence, shot, character, prop, etc. All of these behave identically in the pipeline. Asset names need to be absolutely unique within the project because they are their key identifier. +OpenPype has limitation regarging duplicated names. Name of assets must be unique across whole project. + ### Subset Usually, an asset needs to be created in multiple *'flavours'*. A character might have multiple different looks, model needs to be published in different resolutions, a standard animation rig might not be usable in a crowd system and so on. 'Subsets' are here to accommodate all this variety that might be needed within a single asset. A model might have subset: *'main'*, *'proxy'*, *'sculpt'*, while data of *'look'* family could have subsets *'main'*, *'dirty'*, *'damaged'*. Subsets have some recommendations for their names, but ultimately it's up to the artist to use them for separation of publishes when needed. @@ -24,6 +26,11 @@ A numbered iteration of a given subset. Each version contains at least one [repr Each published variant can come out of the software in multiple representations. All of them hold exactly the same data, but in different formats. A model, for example, might be saved as `.OBJ`, Alembic, Maya geometry or as all of them, to be ready for pickup in any other applications supporting these formats. + +#### Naming convention + +At this moment names of assets, tasks, subsets or representations can contain only letters, numbers and underscore. + ### Family Each published [subset][3b89d8e0] can have exactly one family assigned to it. Family determines the type of data that the subset holds. Family doesn't dictate the file type, but can enforce certain technical specifications. For example OpenPype default configuration expects `model` family to only contain geometry without any shaders or joints when it is published. From 2591c1fad5355b824198389dae7c918061e67125 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 19 Aug 2022 21:46:36 +0200 Subject: [PATCH 047/101] add link to key concepts into system introduction --- website/docs/system_introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/system_introduction.md b/website/docs/system_introduction.md index db297b527a..b8a2cea487 100644 --- a/website/docs/system_introduction.md +++ b/website/docs/system_introduction.md @@ -17,7 +17,7 @@ various usage scenarios. You can find detailed breakdown of technical requirements [here](dev_requirements), but in general OpenPype should be able to operate in most studios fairly quickly. The main obstacles are usually related to workflows and habits, that -might now be fully compatible with what OpenPype is expecting or enforcing. +might now be fully compatible with what OpenPype is expecting or enforcing. It is recommended to go through artists [key concepts](artist_concepts) to get idea about basics. Keep in mind that if you run into any workflows that are not supported, it's usually just because we haven't hit that particular case and it can most likely be added upon request. From 3beca31c61317c068e9d667c8b0817265c3e26f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 10:57:54 +0200 Subject: [PATCH 048/101] OP-3723 - introduced max_downscale_size value to Settings Studios might want to set maximum size to resize for ffmpeg to work based on OS or resources. --- .../hosts/photoshop/plugins/publish/extract_review.py | 8 ++++---- .../settings/defaults/project_settings/photoshop.json | 1 + .../projects_schema/schema_project_photoshop.json | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py index 60ae575b0a..5d37c86ed8 100644 --- a/openpype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -30,6 +30,7 @@ class ExtractReview(openpype.api.Extractor): jpg_options = None mov_options = None make_image_sequence = None + max_downscale_size = 8192 def process(self, instance): staging_dir = self.staging_dir(instance) @@ -143,14 +144,12 @@ class ExtractReview(openpype.api.Extractor): Ffmpeg has max size 16384x16384. Saved image(s) must be resized to be used as a source for thumbnail or review mov. """ - # 16384x16384 actually didn't work because int overflow - max_ffmpeg_size = 8192 Image.MAX_IMAGE_PIXELS = None first_url = os.path.join(staging_dir, processed_img_names[0]) with Image.open(first_url) as im: width, height = im.size - if width > max_ffmpeg_size or height > max_ffmpeg_size: + if width > self.max_downscale_size or height > self.max_downscale_size: resized_dir = os.path.join(staging_dir, "resized") os.mkdir(resized_dir) source_files_pattern = os.path.join(resized_dir, @@ -159,7 +158,8 @@ class ExtractReview(openpype.api.Extractor): source_url = os.path.join(staging_dir, file_name) with Image.open(source_url) as res_img: # 'thumbnail' automatically keeps aspect ratio - res_img.thumbnail((max_ffmpeg_size, max_ffmpeg_size), + res_img.thumbnail((self.max_downscale_size, + self.max_downscale_size), Image.ANTIALIAS) res_img.save(os.path.join(resized_dir, file_name)) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index d9b7a8083f..758ac64a35 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -32,6 +32,7 @@ }, "ExtractReview": { "make_image_sequence": false, + "max_downscale_size": 8192, "jpg_options": { "tags": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index badf94229b..49860301b6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -186,6 +186,15 @@ "key": "make_image_sequence", "label": "Makes an image sequence instead of a flatten image" }, + { + "type": "number", + "key": "max_downscale_size", + "label": "Maximum size of sources for review", + "tooltip": "FFMpeg can only handle limited resolution for creation of review and/or thumbnail", + "minimum": 300, + "maximum": 16384, + "decimal": 0 + }, { "type": "dict", "collapsible": false, From f4106714aeabd13eec6d8085cb3f3be39feb4f00 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 11:27:19 +0200 Subject: [PATCH 049/101] added dialogs for 2 other cases --- openpype/tools/settings/settings/dialogs.py | 64 +++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/openpype/tools/settings/settings/dialogs.py b/openpype/tools/settings/settings/dialogs.py index dea056b89d..a3eed68ae3 100644 --- a/openpype/tools/settings/settings/dialogs.py +++ b/openpype/tools/settings/settings/dialogs.py @@ -113,3 +113,67 @@ class SettingsUIOpenedElsewhere(BaseInfoDialog): take_control_btn, view_mode_btn ] + + +class SettingsLastSavedChanged(BaseInfoDialog): + width = 500 + height = 300 + + def __init__(self, info_obj, parent=None): + title = "Settings has changed" + message = ( + "Settings has changed while you had opened this settings session." + "

It is recommended to refresh settings" + " and re-apply changes in the new session." + ) + super(SettingsLastSavedChanged, self).__init__( + message, title, info_obj, parent + ) + + def _on_save(self): + self._result = 1 + self.close() + + def _on_close(self): + self._result = 0 + self.close() + + def get_buttons(self, parent): + close_btn = QtWidgets.QPushButton( + "Close", parent + ) + save_btn = QtWidgets.QPushButton( + "Save anyway", parent + ) + + close_btn.clicked.connect(self._on_close) + save_btn.clicked.connect(self._on_save) + + return [ + close_btn, + save_btn + ] + + +class SettingsControlTaken(BaseInfoDialog): + width = 500 + height = 300 + + def __init__(self, info_obj, parent=None): + title = "Settings control taken" + message = ( + "Someone took control over your settings." + "

It is not possible to save changes of currently" + " opened session. Copy changes you want to keep and hit refresh." + ) + super(SettingsControlTaken, self).__init__( + message, title, info_obj, parent + ) + + def _on_confirm(self): + self.close() + + def get_buttons(self, parent): + confirm_btn = QtWidgets.QPushButton("Understand", parent) + confirm_btn.clicked.connect(self._on_confirm) + return [confirm_btn] From 537bfaa8d683213f03a78b962d1767698947c390 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 11:27:45 +0200 Subject: [PATCH 050/101] removed print --- openpype/tools/settings/settings/window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 2750785535..a907a034d1 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -87,7 +87,6 @@ class SettingsController: ) def update_last_opened_info(self): - print("update_last_opened_info") last_opened_info = get_last_opened_info() enabled = False if ( From 80b5c0c064c47d4bab6e870759a2fc4caf54cfd9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 11:28:00 +0200 Subject: [PATCH 051/101] categories have checks related to last saved settings --- .../tools/settings/settings/categories.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index fd95b4ca71..f4b2c13a12 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -36,6 +36,11 @@ from openpype.settings.entities.op_version_entity import ( ) from openpype.settings import SaveWarningExc +from openpype.settings.lib import ( + get_system_last_saved_info, + get_project_last_saved_info, +) +from .dialogs import SettingsLastSavedChanged, SettingsControlTaken from .widgets import ( ProjectListWidget, VersionAction @@ -205,6 +210,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): if enabled is self._edit_mode: return + was_false = self._edit_mode is False self._edit_mode = enabled self.save_btn.setEnabled(enabled and not self._reset_crashed) @@ -218,6 +224,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.save_btn.setToolTip(tooltip) + # Reset when last saved information has changed + if was_false and not self._check_last_saved_info(): + self.reset() + @property def state(self): return self._state @@ -748,7 +758,24 @@ class SettingsCategoryWidget(QtWidgets.QWidget): """Callback on any tab widget save.""" return + def _check_last_saved_info(self): + raise NotImplementedError(( + "{} does not have implemented '_check_last_saved_info'" + ).format(self.__class__.__name__)) + def _save(self): + self._controller.update_last_opened_info() + if not self._controller.opened_info: + dialog = SettingsControlTaken(self._last_saved_info, self) + dialog.exec_() + return + + if not self._check_last_saved_info(): + dialog = SettingsLastSavedChanged(self._last_saved_info, self) + dialog.exec_() + if dialog.result() == 0: + return + # Don't trigger restart if defaults are modified if self.is_modifying_defaults: require_restart = False @@ -807,6 +834,13 @@ class SystemWidget(SettingsCategoryWidget): self._actions = [] super(SystemWidget, self).__init__(*args, **kwargs) + def _check_last_saved_info(self): + if self.is_modifying_defaults: + return True + + last_saved_info = get_system_last_saved_info() + return self._last_saved_info == last_saved_info + def contain_category_key(self, category): if category == "system_settings": return True @@ -821,6 +855,10 @@ class SystemWidget(SettingsCategoryWidget): ) entity.on_change_callbacks.append(self._on_entity_change) self.entity = entity + last_saved_info = None + if not self.is_modifying_defaults: + last_saved_info = get_system_last_saved_info() + self._last_saved_info = last_saved_info try: if self.is_modifying_defaults: entity.set_defaults_state() @@ -854,6 +892,13 @@ class ProjectWidget(SettingsCategoryWidget): def __init__(self, *args, **kwargs): super(ProjectWidget, self).__init__(*args, **kwargs) + def _check_last_saved_info(self): + if self.is_modifying_defaults: + return True + + last_saved_info = get_project_last_saved_info(self.project_name) + return self._last_saved_info == last_saved_info + def contain_category_key(self, category): if category in ("project_settings", "project_anatomy"): return True @@ -933,6 +978,11 @@ class ProjectWidget(SettingsCategoryWidget): entity.on_change_callbacks.append(self._on_entity_change) self.project_list_widget.set_entity(entity) self.entity = entity + + last_saved_info = None + if not self.is_modifying_defaults: + last_saved_info = get_project_last_saved_info(self.project_name) + self._last_saved_info = last_saved_info try: if self.is_modifying_defaults: self.entity.set_defaults_state() From 7cd6a423ead42bae21a2fa2e491bf32870402fe8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 11:55:04 +0200 Subject: [PATCH 052/101] fix attribute name --- openpype/settings/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 1b59531943..09f36aa16e 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -784,9 +784,9 @@ class MongoSettingsHandler(SettingsHandler): "last_saved_info": last_saved_info.to_document_data() } if not system_settings_doc: - self.collections.insert_one(new_system_settings_doc) + self.collection.insert_one(new_system_settings_doc) else: - self.collections.update_one( + self.collection.update_one( {"_id": system_settings_doc["_id"]}, {"$set": new_system_settings_doc} ) From c3fe68c58a40fcca6f998b1d17e67aa690404349 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 12:02:21 +0200 Subject: [PATCH 053/101] converted unreal to openpype module --- openpype/hosts/unreal/__init__.py | 26 ++++----------------- openpype/hosts/unreal/module.py | 39 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 openpype/hosts/unreal/module.py diff --git a/openpype/hosts/unreal/__init__.py b/openpype/hosts/unreal/__init__.py index 10e9c5100e..41222f4f94 100644 --- a/openpype/hosts/unreal/__init__.py +++ b/openpype/hosts/unreal/__init__.py @@ -1,24 +1,6 @@ -import os -import openpype.hosts -from openpype.lib.applications import Application +from .module import UnrealModule -def add_implementation_envs(env: dict, _app: Application) -> None: - """Modify environments to contain all required for implementation.""" - # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation - - ue_plugin = "UE_5.0" if _app.name[:1] == "5" else "UE_4.7" - unreal_plugin_path = os.path.join( - os.path.dirname(os.path.abspath(openpype.hosts.__file__)), - "unreal", "integration", ue_plugin - ) - if not env.get("OPENPYPE_UNREAL_PLUGIN"): - env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path - - # Set default environments if are not set via settings - defaults = { - "OPENPYPE_LOG_NO_COLORS": "True" - } - for key, value in defaults.items(): - if not env.get(key): - env[key] = value +__all__ = ( + "UnrealModule", +) diff --git a/openpype/hosts/unreal/module.py b/openpype/hosts/unreal/module.py new file mode 100644 index 0000000000..a30c9e9e36 --- /dev/null +++ b/openpype/hosts/unreal/module.py @@ -0,0 +1,39 @@ +import os +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class UnrealModule(OpenPypeModule, IHostModule): + name = "unreal" + host_name = "unreal" + + def initialize(self, module_settings): + self.enabled = True + + def add_implementation_envs(self, env, app) -> None: + """Modify environments to contain all required for implementation.""" + # Set OPENPYPE_UNREAL_PLUGIN required for Unreal implementation + + ue_plugin = "UE_5.0" if app.name[:1] == "5" else "UE_4.7" + unreal_plugin_path = os.path.join( + UNREAL_ROOT_DIR, "integration", ue_plugin + ) + if not env.get("OPENPYPE_UNREAL_PLUGIN"): + env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path + + # Set default environments if are not set via settings + defaults = { + "OPENPYPE_LOG_NO_COLORS": "True" + } + for key, value in defaults.items(): + if not env.get(key): + env[key] = value + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(UNREAL_ROOT_DIR, "hooks") + ] From 44b9146e4fa43188a3065090797a05b319d58e59 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 12:16:50 +0200 Subject: [PATCH 054/101] use Unreal host as class instead of module --- openpype/hosts/unreal/api/__init__.py | 4 ++- openpype/hosts/unreal/api/pipeline.py | 27 +++++++++++++++++++ .../UE_4.7/Content/Python/init_unreal.py | 4 ++- .../UE_5.0/Content/Python/init_unreal.py | 4 ++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py index ede71aa218..870982f5f9 100644 --- a/openpype/hosts/unreal/api/__init__.py +++ b/openpype/hosts/unreal/api/__init__.py @@ -19,6 +19,7 @@ from .pipeline import ( show_tools_dialog, show_tools_popup, instantiate, + UnrealHost, ) __all__ = [ @@ -36,5 +37,6 @@ __all__ = [ "show_experimental_tools", "show_tools_dialog", "show_tools_popup", - "instantiate" + "instantiate", + "UnrealHost", ] diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index bbca7916d3..ee4282e357 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -14,6 +14,7 @@ from openpype.pipeline import ( ) from openpype.tools.utils import host_tools import openpype.hosts.unreal +from openpypr.host import HostBase, ILoadHost import unreal # noqa @@ -29,6 +30,32 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") +class UnrealHost(HostBase, ILoadHost): + """Unreal host implementation. + + For some time this class will re-use functions from module based + implementation for backwards compatibility of older unreal projects. + """ + + name = "unreal" + + def install(self): + install() + + def get_containers(self): + return ls() + + def show_tools_popup(self): + """Show tools popup with actions leading to show other tools.""" + + show_tools_popup() + + def show_tools_dialog(self): + """Show tools dialog with actions leading to show other tools.""" + + show_tools_dialog() + + def install(): """Install Unreal configuration for OpenPype.""" print("-=" * 40) diff --git a/openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py index 4bb03b07ed..b85f970699 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py +++ b/openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py @@ -3,7 +3,9 @@ import unreal openpype_detected = True try: from openpype.pipeline import install_host - from openpype.hosts.unreal import api as openpype_host + from openpype.hosts.unreal.api import UnrealHost + + openpype_host = UnrealHost() except ImportError as exc: openpype_host = None openpype_detected = False diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py index 4bb03b07ed..b85f970699 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py +++ b/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py @@ -3,7 +3,9 @@ import unreal openpype_detected = True try: from openpype.pipeline import install_host - from openpype.hosts.unreal import api as openpype_host + from openpype.hosts.unreal.api import UnrealHost + + openpype_host = UnrealHost() except ImportError as exc: openpype_host = None openpype_detected = False From d98bc3509057f2c59a278656ba6360a10825b57d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 13:22:38 +0200 Subject: [PATCH 055/101] implemented 'get_workfile_extensions' in unreal module --- openpype/hosts/unreal/module.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/unreal/module.py b/openpype/hosts/unreal/module.py index a30c9e9e36..aa08c8c130 100644 --- a/openpype/hosts/unreal/module.py +++ b/openpype/hosts/unreal/module.py @@ -37,3 +37,6 @@ class UnrealModule(OpenPypeModule, IHostModule): return [ os.path.join(UNREAL_ROOT_DIR, "hooks") ] + + def get_workfile_extensions(self): + return [".uproject"] From 969241426ad6d53e916b2c8d140742b9bb80f635 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:19:17 +0200 Subject: [PATCH 056/101] moved and modified 'compute_session_changes' into context tools --- openpype/pipeline/context_tools.py | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 5f763cd249..66bf33e821 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -445,3 +445,60 @@ def get_custom_workfile_template_from_session( session["AVALON_APP"], project_settings=project_settings ) + + +def compute_session_changes( + session, asset_doc, task_name, template_key=None +): + """Compute the changes for a session object on task under asset. + + Function does not change the session object, only returns changes. + + Args: + session (Dict[str, str]): The initial session to compute changes to. + This is required for computing the full Work Directory, as that + also depends on the values that haven't changed. + asset_doc (Dict[str, Any]): Asset document to switch to. + task_name (str): Name of task to switch to. + template_key (Union[str, None]): Prepare workfile template key in + anatomy templates. + + Returns: + Dict[str, str]: Changes in the Session dictionary. + """ + + changes = {} + + # Get asset document and asset + if not asset_doc: + task_name = None + asset_name = None + else: + asset_name = asset_doc["name"] + + # Detect any changes compared session + mapping = { + "AVALON_ASSET": asset_name, + "AVALON_TASK": task_name, + } + changes = { + key: value + for key, value in mapping.items() + if value != session.get(key) + } + if not changes: + return changes + + # Compute work directory (with the temporary changed session so far) + changed_session = session.copy() + changed_session.update(changes) + + workdir = None + if asset_doc: + workdir = get_workdir_from_session( + changed_session, template_key + ) + + changes["AVALON_WORKDIR"] = workdir + + return changes From 097546e429fa7c7afdb4abfb19ef8458979173c6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:19:53 +0200 Subject: [PATCH 057/101] moved 'update_current_task' to context tools and renamed to 'change_current_context' --- openpype/pipeline/context_tools.py | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index 66bf33e821..00fe353208 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -16,6 +16,7 @@ from openpype.client import ( get_asset_by_name, version_is_latest, ) +from openpype.lib.events import emit_event from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings @@ -502,3 +503,46 @@ def compute_session_changes( changes["AVALON_WORKDIR"] = workdir return changes + + +def change_current_context(asset_doc, task_name, template_key=None): + """Update active Session to a new task work area. + + This updates the live Session to a different task under asset. + + Args: + asset_doc (Dict[str, Any]): The asset document to set. + task_name (str): The task to set under asset. + template_key (Union[str, None]): Prepared template key to be used for + workfile template in Anatomy. + + Returns: + Dict[str, str]: The changed key, values in the current Session. + """ + + changes = compute_session_changes( + legacy_io.Session, + asset_doc, + task_name, + template_key=template_key + ) + + # Update the Session and environments. Pop from environments all keys with + # value set to None. + for key, value in changes.items(): + legacy_io.Session[key] = value + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value + + data = changes.copy() + # Convert env keys to human readable keys + data["project_name"] = legacy_io.Session["AVALON_PROJECT"] + data["asset_name"] = legacy_io.Session["AVALON_ASSET"] + data["task_name"] = legacy_io.Session["AVALON_TASK"] + + # Emit session change + emit_event("taskChanged", data) + + return changes From 6257fcb7774857e23abcabea6261193c8caf35af Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:34:43 +0200 Subject: [PATCH 058/101] marked 'compute_session_changes' and 'update_current_task' as deprecated in openpype.lib --- openpype/lib/avalon_context.py | 98 ++++++++++------------------------ 1 file changed, 27 insertions(+), 71 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index eed17fce9d..31fdf4c596 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,6 +7,8 @@ import logging import functools import warnings +import six + from openpype.client import ( get_project, get_assets, @@ -526,7 +528,7 @@ def template_data_from_session(session=None): return get_template_data_from_session(session) -@with_pipeline_io +@deprecated("openpype.pipeline.context_tools.compute_session_changes") def compute_session_changes( session, task=None, asset=None, app=None, template_key=None ): @@ -547,54 +549,24 @@ def compute_session_changes( Returns: dict: The required changes in the Session dictionary. + + Deprecated: + Function will be removed after release version 3.16.* """ - from openpype.pipeline.context_tools import get_workdir_from_session + from openpype.pipeline import legacy_io + from openpype.pipeline.context_tools import compute_session_changes - changes = dict() + if isinstance(asset, six.string_types): + project_name = legacy_io.active_project() + asset = get_asset_by_name(project_name, asset) - # If no changes, return directly - if not any([task, asset, app]): - return changes - - # Get asset document and asset - asset_document = None - asset_tasks = None - if isinstance(asset, dict): - # Assume asset database document - asset_document = asset - asset_tasks = asset_document.get("data", {}).get("tasks") - asset = asset["name"] - - if not asset_document or not asset_tasks: - # Assume asset name - project_name = session["AVALON_PROJECT"] - asset_document = get_asset_by_name( - project_name, asset, fields=["data.tasks"] - ) - assert asset_document, "Asset must exist" - - # Detect any changes compared session - mapping = { - "AVALON_ASSET": asset, - "AVALON_TASK": task, - "AVALON_APP": app, - } - changes = { - key: value - for key, value in mapping.items() - if value and value != session.get(key) - } - if not changes: - return changes - - # Compute work directory (with the temporary changed session so far) - _session = session.copy() - _session.update(changes) - - changes["AVALON_WORKDIR"] = get_workdir_from_session(_session) - - return changes + return compute_session_changes( + session, + asset, + task, + template_key + ) @deprecated("openpype.pipeline.context_tools.get_workdir_from_session") @@ -604,7 +576,7 @@ def get_workdir_from_session(session=None, template_key=None): return get_workdir_from_session(session, template_key) -@with_pipeline_io +@deprecated("openpype.pipeline.context_tools.change_current_context") def update_current_task(task=None, asset=None, app=None, template_key=None): """Update active Session to a new task work area. @@ -617,35 +589,19 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): Returns: dict: The changed key, values in the current Session. + + Deprecated: + Function will be removed after release version 3.16.* """ - changes = compute_session_changes( - legacy_io.Session, - task=task, - asset=asset, - app=app, - template_key=template_key - ) + from openpype.pipeline import legacy_io + from openpype.pipeline.context_tools import change_current_context - # Update the Session and environments. Pop from environments all keys with - # value set to None. - for key, value in changes.items(): - legacy_io.Session[key] = value - if value is None: - os.environ.pop(key, None) - else: - os.environ[key] = value + project_name = legacy_io.acitve_project() + if isinstance(asset, six.string_types): + asset = get_asset_by_name(project_name, asset) - data = changes.copy() - # Convert env keys to human readable keys - data["project_name"] = legacy_io.Session["AVALON_PROJECT"] - data["asset_name"] = legacy_io.Session["AVALON_ASSET"] - data["task_name"] = legacy_io.Session["AVALON_TASK"] - - # Emit session change - emit_event("taskChanged", data) - - return changes + return change_current_context(asset, task, template_key) @deprecated("openpype.client.get_workfile_info") From 38d2233b3f03ba8f151ecd74449f352ef41b8fea Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:37:14 +0200 Subject: [PATCH 059/101] added or modified removement version in Deprecated category of deprecated functions --- openpype/lib/avalon_context.py | 69 ++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 31fdf4c596..ca8a04b9d0 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -180,7 +180,7 @@ def is_latest(representation): bool: Whether the representation is of latest version. Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ from openpype.pipeline.context_tools import is_representation_from_latest @@ -193,7 +193,7 @@ def any_outdated(): """Return whether the current scene has any outdated content. Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ from openpype.pipeline.load import any_outdated_containers @@ -214,7 +214,7 @@ def get_asset(asset_name=None): (MongoDB document) Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ from openpype.pipeline.context_tools import get_current_project_asset @@ -226,7 +226,7 @@ def get_asset(asset_name=None): def get_system_general_anatomy_data(system_settings=None): """ Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ from openpype.pipeline.template_data import get_general_template_data @@ -298,7 +298,7 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): dict: Last version document for entered. Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ if not project_name: @@ -346,6 +346,9 @@ def get_workfile_template_key_from_context( Raises: ValueError: When both 'dbcon' and 'project_name' were not passed. + + Deprecated: + Function will be removed after release version 3.16.* """ from openpype.pipeline.workfile import ( @@ -389,6 +392,9 @@ def get_workfile_template_key( Raises: ValueError: When both 'project_name' and 'project_settings' were not passed. + + Deprecated: + Function will be removed after release version 3.16.* """ from openpype.pipeline.workfile import get_workfile_template_key @@ -413,7 +419,7 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): dict: Data prepared for filling workdir template. Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ from openpype.pipeline.template_data import get_template_data @@ -449,6 +455,9 @@ def get_workdir_with_workdir_data( Raises: ValueError: When both `anatomy` and `project_name` are set to None. + + Deprecated: + Function will be removed after release version 3.15.* """ if not anatomy and not project_name: @@ -494,6 +503,9 @@ def get_workdir( Returns: TemplateResult: Workdir path. + + Deprecated: + Function will be removed after release version 3.15.* """ from openpype.pipeline.workfile import get_workdir @@ -520,7 +532,7 @@ def template_data_from_session(session=None): dict: All available data from session. Deprecated: - Function will be removed after release version 3.14.* + Function will be removed after release version 3.15.* """ from openpype.pipeline.context_tools import get_template_data_from_session @@ -571,6 +583,21 @@ def compute_session_changes( @deprecated("openpype.pipeline.context_tools.get_workdir_from_session") def get_workdir_from_session(session=None, template_key=None): + """Calculate workdir path based on session data. + + Args: + session (Union[None, Dict[str, str]]): Session to use. If not passed + current context session is used (from legacy_io). + template_key (Union[str, None]): Precalculate template key to define + workfile template name in Anatomy. + + Returns: + str: Workdir path. + + Deprecated: + Function will be removed after release version 3.16.* + """ + from openpype.pipeline.context_tools import get_workdir_from_session return get_workdir_from_session(session, template_key) @@ -620,6 +647,9 @@ def get_workfile_doc(asset_id, task_name, filename, dbcon=None): Returns: dict: Workfile document or None. + + Deprecated: + Function will be removed after release version 3.15.* """ # Use legacy_io if dbcon is not entered @@ -730,6 +760,11 @@ def save_workfile_data_to_doc(workfile_doc, data, dbcon=None): @deprecated("openpype.pipeline.workfile.BuildWorkfile") def BuildWorkfile(): + """Build workfile class was moved to workfile pipeline. + + Deprecated: + Function will be removed after release version 3.16.* + """ from openpype.pipeline.workfile import BuildWorkfile return BuildWorkfile() @@ -772,10 +807,7 @@ def change_timer_to_current_context(): Deprecated: This method is specific for TimersManager module so please use the functionality from there. Function will be removed after release - version 3.14.* - - TODO: - - use TimersManager's static method instead of reimplementing it here + version 3.15.* """ from openpype.pipeline import legacy_io @@ -890,6 +922,9 @@ def get_custom_workfile_template_by_context( Returns: str: Path to template or None if none of profiles match current context. (Existence of formatted path is not validated.) + + Deprecated: + Function will be removed after release version 3.16.* """ if anatomy is None: @@ -948,6 +983,9 @@ def get_custom_workfile_template_by_string_context( Returns: str: Path to template or None if none of profiles match current context. (Existence of formatted path is not validated.) + + Deprecated: + Function will be removed after release version 3.16.* """ project_name = None @@ -982,6 +1020,9 @@ def get_custom_workfile_template(template_profiles): Returns: str: Path to template or None if none of profiles match current context. (Existence of formatted path is not validated.) + + Deprecated: + Function will be removed after release version 3.16.* """ from openpype.pipeline import legacy_io @@ -1010,6 +1051,9 @@ def get_last_workfile_with_version( Returns: tuple: Last workfile with version if there is any otherwise returns (None, None). + + Deprecated: + Function will be removed after release version 3.16.* """ from openpype.pipeline.workfile import get_last_workfile_with_version @@ -1036,6 +1080,9 @@ def get_last_workfile( Returns: str: Last or first workfile as filename of full path to filename. + + Deprecated: + Function will be removed after release version 3.16.* """ from openpype.pipeline.workfile import get_last_workfile From d74eb5961ee67a8b304e4fc616195b24021f9b96 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:42:45 +0200 Subject: [PATCH 060/101] fix typo --- openpype/lib/avalon_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index ca8a04b9d0..0f4f04e4d3 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -624,7 +624,7 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): from openpype.pipeline import legacy_io from openpype.pipeline.context_tools import change_current_context - project_name = legacy_io.acitve_project() + project_name = legacy_io.active_project() if isinstance(asset, six.string_types): asset = get_asset_by_name(project_name, asset) From 867f91d9f4fff0e57ec185e6dfafeb311de9fe08 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:42:52 +0200 Subject: [PATCH 061/101] removed unused import --- openpype/lib/avalon_context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 0f4f04e4d3..f08adb5470 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -17,7 +17,6 @@ from openpype.client import ( get_workfile_info, ) from .profiles_filtering import filter_profiles -from .events import emit_event from .path_templates import StringTemplate legacy_io = None From 2fea675167c6f1a2441d014171918fd00c56288f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 14:58:52 +0200 Subject: [PATCH 062/101] use new functions in workfiles tool --- openpype/tools/workfiles/files_widget.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a4109c511e..a5d5b14bb6 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -14,15 +14,15 @@ from openpype.lib import ( emit_event, create_workdir_extra_folders, ) -from openpype.lib.avalon_context import ( - update_current_task, - compute_session_changes -) from openpype.pipeline import ( registered_host, legacy_io, Anatomy, ) +from openpype.pipeline.context_tools import ( + compute_session_changes, + change_current_context +) from openpype.pipeline.workfile import get_workfile_template_key from .model import ( @@ -408,8 +408,8 @@ class FilesWidget(QtWidgets.QWidget): ) changes = compute_session_changes( session, - asset=self._get_asset_doc(), - task=self._task_name, + self._get_asset_doc(), + self._task_name, template_key=self.template_key ) session.update(changes) @@ -422,8 +422,8 @@ class FilesWidget(QtWidgets.QWidget): session = legacy_io.Session.copy() changes = compute_session_changes( session, - asset=self._get_asset_doc(), - task=self._task_name, + self._get_asset_doc(), + self._task_name, template_key=self.template_key ) if not changes: @@ -431,9 +431,9 @@ class FilesWidget(QtWidgets.QWidget): # to avoid any unwanted Task Changed callbacks to be triggered. return - update_current_task( - asset=self._get_asset_doc(), - task=self._task_name, + change_current_context( + self._get_asset_doc(), + self._task_name, template_key=self.template_key ) From 9b60b9faa89c8551c88977c42ad9f404869c7680 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 15:19:26 +0200 Subject: [PATCH 063/101] change title if in view mode --- openpype/tools/settings/settings/window.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index a907a034d1..77a2f64dac 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -105,6 +105,7 @@ class MainWidget(QtWidgets.QWidget): widget_width = 1000 widget_height = 600 + window_title = "OpenPype Settings" def __init__(self, user_role, parent=None, reset_on_show=True): super(MainWidget, self).__init__(parent) @@ -122,7 +123,7 @@ class MainWidget(QtWidgets.QWidget): self._password_dialog = None self.setObjectName("SettingsMainWidget") - self.setWindowTitle("OpenPype Settings") + self.setWindowTitle(self.window_title) self.resize(self.widget_width, self.widget_height) @@ -155,6 +156,11 @@ class MainWidget(QtWidgets.QWidget): self._shadow_widget = ShadowWidget("Working...", self) self._shadow_widget.setVisible(False) + controller.event_system.add_callback( + "edit.mode.changed", + self._edit_mode_changed + ) + header_tab_widget.currentChanged.connect(self._on_tab_changed) search_dialog.path_clicked.connect(self._on_search_path_clicked) @@ -301,6 +307,12 @@ class MainWidget(QtWidgets.QWidget): entity = widget.entity self._search_dialog.set_root_entity(entity) + def _edit_mode_changed(self, event): + title = self.window_title + if not event["edit_mode"]: + title += " [View only]" + self.setWindowTitle(title) + def _on_tab_changed(self): self._update_search_dialog() From 1b64160644a1af0a8fd4349814a1c5e3fe496b85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 15:21:48 +0200 Subject: [PATCH 064/101] use pretty time instead of timestamp --- openpype/tools/settings/settings/dialogs.py | 25 ++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/dialogs.py b/openpype/tools/settings/settings/dialogs.py index a3eed68ae3..f25374a48c 100644 --- a/openpype/tools/settings/settings/dialogs.py +++ b/openpype/tools/settings/settings/dialogs.py @@ -1,5 +1,7 @@ from Qt import QtWidgets, QtCore +from openpype.tools.utils.delegates import pretty_date + class BaseInfoDialog(QtWidgets.QDialog): width = 600 @@ -34,13 +36,17 @@ class BaseInfoDialog(QtWidgets.QDialog): ("Host IP", info_obj.hostip), ("System name", info_obj.system_name), ("Local ID", info_obj.local_id), - ("Time Stamp", info_obj.timestamp), ): other_information_layout.addRow( label, QtWidgets.QLabel(value, other_information) ) + timestamp_label = QtWidgets.QLabel( + pretty_date(info_obj.timestamp_obj), other_information + ) + other_information_layout.addRow("Time", timestamp_label) + footer_widget = QtWidgets.QWidget(self) buttons_widget = QtWidgets.QWidget(footer_widget) @@ -64,10 +70,27 @@ class BaseInfoDialog(QtWidgets.QDialog): layout.addWidget(separator_widget_2, 0) layout.addWidget(footer_widget, 0) + timestamp_timer = QtCore.QTimer() + timestamp_timer.setInterval(1000) + timestamp_timer.timeout.connect(self._on_timestamp_timer) + + self._timestamp_label = timestamp_label + self._timestamp_timer = timestamp_timer + def showEvent(self, event): super(BaseInfoDialog, self).showEvent(event) + self._timestamp_timer.start() self.resize(self.width, self.height) + def closeEvent(self, event): + self._timestamp_timer.stop() + super(BaseInfoDialog, self).closeEvent(event) + + def _on_timestamp_timer(self): + self._timestamp_label.setText( + pretty_date(self._info_obj.timestamp_obj) + ) + def result(self): return self._result From 5df9c1b41f99fd2d587355c54b5c15eef53ce588 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 16:48:32 +0200 Subject: [PATCH 065/101] Added default variant to workfile collectors for PS|AE Will only propagate in workfile subset (and final published name of workfile) if {variant} is used in subset name template. (By default it isn't.) --- .../hosts/aftereffects/plugins/publish/collect_workfile.py | 4 +++- openpype/hosts/photoshop/plugins/publish/collect_workfile.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py index 9cb6900b0a..fef5448a4c 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py @@ -11,6 +11,8 @@ class CollectWorkfile(pyblish.api.ContextPlugin): label = "Collect After Effects Workfile Instance" order = pyblish.api.CollectorOrder + 0.1 + default_variant = "Main" + def process(self, context): existing_instance = None for instance in context: @@ -71,7 +73,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): family = "workfile" subset = get_subset_name_with_asset_doc( family, - "", + self.default_variant, context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], diff --git a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py index e4f0a07b34..6599f5c96e 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py @@ -11,6 +11,8 @@ class CollectWorkfile(pyblish.api.ContextPlugin): label = "Collect Workfile" hosts = ["photoshop"] + default_variant = "Main" + def process(self, context): existing_instance = None for instance in context: @@ -22,7 +24,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin): family = "workfile" subset = get_subset_name_with_asset_doc( family, - "", + self.default_variant, context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], From 9434cb43c1d480ea523093907ad0fbe2a4a24744 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 17:07:46 +0200 Subject: [PATCH 066/101] fix published workfile filtering --- openpype/tools/workfiles/model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index d5b7cef339..9a7fd659a9 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -299,7 +299,6 @@ class PublishFilesModel(QtGui.QStandardItemModel): self.project_name, asset_ids=[self._asset_id], fields=["_id", "name"] - ) subset_ids = [subset_doc["_id"] for subset_doc in subset_docs] @@ -329,7 +328,9 @@ class PublishFilesModel(QtGui.QStandardItemModel): # extension extensions = [ext.replace(".", "") for ext in self._file_extensions] repre_docs = get_representations( - self.project_name, version_ids, extensions + self.project_name, + version_ids=version_ids, + context_filters={"ext": extensions} ) # Filter queried representations by task name if task is set From 45da6cf5d0fbdb31a91eef2115e04299860f5f48 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 17:50:06 +0200 Subject: [PATCH 067/101] Added possibility to propagate collected variant context.data["variant"] might be filled only by collect_batch_data, which should take precedence --- openpype/hosts/photoshop/plugins/publish/collect_workfile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py index 6599f5c96e..9cf6d5227e 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py @@ -22,9 +22,11 @@ class CollectWorkfile(pyblish.api.ContextPlugin): break family = "workfile" + # context.data["variant"] might come only from collect_batch_data + variant = context.data.get("variant") or self.default_variant subset = get_subset_name_with_asset_doc( family, - self.default_variant, + variant, context.data["anatomyData"]["task"]["name"], context.data["assetEntity"], context.data["anatomyData"]["project"]["name"], From 20d08049672cd328f4a4ac093b01864ce61d270f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Aug 2022 17:50:59 +0200 Subject: [PATCH 068/101] Fix typo Co-authored-by: Simone Barbieri --- openpype/hosts/unreal/api/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py index ee4282e357..d396b64072 100644 --- a/openpype/hosts/unreal/api/pipeline.py +++ b/openpype/hosts/unreal/api/pipeline.py @@ -14,7 +14,7 @@ from openpype.pipeline import ( ) from openpype.tools.utils import host_tools import openpype.hosts.unreal -from openpypr.host import HostBase, ILoadHost +from openpype.host import HostBase, ILoadHost import unreal # noqa From 4a4712b02047ff61ea23a90b5348768458b4aa34 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 17:55:57 +0200 Subject: [PATCH 069/101] Added default variant to new creator --- .../photoshop/plugins/create/workfile_creator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/create/workfile_creator.py b/openpype/hosts/photoshop/plugins/create/workfile_creator.py index 43302329f1..ce0245d5c6 100644 --- a/openpype/hosts/photoshop/plugins/create/workfile_creator.py +++ b/openpype/hosts/photoshop/plugins/create/workfile_creator.py @@ -11,6 +11,8 @@ class PSWorkfileCreator(AutoCreator): identifier = "workfile" family = "workfile" + default_variant = "Main" + def get_instance_attr_defs(self): return [] @@ -35,7 +37,6 @@ class PSWorkfileCreator(AutoCreator): existing_instance = instance break - variant = '' project_name = legacy_io.Session["AVALON_PROJECT"] asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] @@ -43,15 +44,17 @@ class PSWorkfileCreator(AutoCreator): if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name + self.default_variant, task_name, asset_doc, + project_name, host_name ) data = { "asset": asset_name, "task": task_name, - "variant": variant + "variant": self.default_variant } data.update(self.get_dynamic_data( - variant, task_name, asset_doc, project_name, host_name + self.default_variant, task_name, asset_doc, + project_name, host_name )) new_instance = CreatedInstance( @@ -67,7 +70,8 @@ class PSWorkfileCreator(AutoCreator): ): asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name + self.default_variant, task_name, asset_doc, + project_name, host_name ) existing_instance["asset"] = asset_name existing_instance["task"] = task_name From c0457a88ea7e67f55818fd4b19db9eec7f887ec4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 17:56:25 +0200 Subject: [PATCH 070/101] Added overwrite old subset name for different context --- openpype/hosts/photoshop/plugins/create/workfile_creator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/photoshop/plugins/create/workfile_creator.py b/openpype/hosts/photoshop/plugins/create/workfile_creator.py index ce0245d5c6..e79d16d154 100644 --- a/openpype/hosts/photoshop/plugins/create/workfile_creator.py +++ b/openpype/hosts/photoshop/plugins/create/workfile_creator.py @@ -75,3 +75,4 @@ class PSWorkfileCreator(AutoCreator): ) existing_instance["asset"] = asset_name existing_instance["task"] = task_name + existing_instance["subset"] = subset_name From be568a0e4140312712ddfe3c8b2b4df573ffd279 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 18:03:17 +0200 Subject: [PATCH 071/101] Added default variant for workfile creator for AE --- .../plugins/create/workfile_creator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index badb3675fd..3b6dee3b83 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -11,6 +11,8 @@ class AEWorkfileCreator(AutoCreator): identifier = "workfile" family = "workfile" + default_variant = "Main" + def get_instance_attr_defs(self): return [] @@ -35,7 +37,6 @@ class AEWorkfileCreator(AutoCreator): existing_instance = instance break - variant = '' project_name = legacy_io.Session["AVALON_PROJECT"] asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] @@ -44,15 +45,17 @@ class AEWorkfileCreator(AutoCreator): if existing_instance is None: asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name + self.default_variant, task_name, asset_doc, + project_name, host_name ) data = { "asset": asset_name, "task": task_name, - "variant": variant + "variant": self.default_variant } data.update(self.get_dynamic_data( - variant, task_name, asset_doc, project_name, host_name + self.default_variant, task_name, asset_doc, + project_name, host_name )) new_instance = CreatedInstance( @@ -69,7 +72,8 @@ class AEWorkfileCreator(AutoCreator): ): asset_doc = get_asset_by_name(project_name, asset_name) subset_name = self.get_subset_name( - variant, task_name, asset_doc, project_name, host_name + self.default_variant, task_name, asset_doc, + project_name, host_name ) existing_instance["asset"] = asset_name existing_instance["task"] = task_name From 0e7c183c1d74380c5cc3b0ac843b5e5d82bf60d8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 22 Aug 2022 18:03:57 +0200 Subject: [PATCH 072/101] Added overwrite subset for different context in AE --- openpype/hosts/aftereffects/plugins/create/workfile_creator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index 3b6dee3b83..f82d15b3c9 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -77,3 +77,4 @@ class AEWorkfileCreator(AutoCreator): ) existing_instance["asset"] = asset_name existing_instance["task"] = task_name + existing_instance["subset"] = subset_name From d91274bb98e470d501837bcd8f3feacc823a25ec Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:07:38 +0200 Subject: [PATCH 073/101] moved traypublish action into traypublisher host --- openpype/hosts/traypublisher/__init__.py | 6 +++++ .../traypublisher/module.py} | 25 ++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 openpype/hosts/traypublisher/__init__.py rename openpype/{modules/traypublish_action.py => hosts/traypublisher/module.py} (70%) diff --git a/openpype/hosts/traypublisher/__init__.py b/openpype/hosts/traypublisher/__init__.py new file mode 100644 index 0000000000..4eb7bf3eef --- /dev/null +++ b/openpype/hosts/traypublisher/__init__.py @@ -0,0 +1,6 @@ +from .module import TrayPublishModule + + +__all__ = ( + "TrayPublishModule", +) diff --git a/openpype/modules/traypublish_action.py b/openpype/hosts/traypublisher/module.py similarity index 70% rename from openpype/modules/traypublish_action.py rename to openpype/hosts/traypublisher/module.py index 39163b8eb8..25012900bc 100644 --- a/openpype/modules/traypublish_action.py +++ b/openpype/hosts/traypublisher/module.py @@ -1,25 +1,24 @@ import os + +import click + from openpype.lib import get_openpype_execute_args from openpype.lib.execute import run_detached_process from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayAction +from openpype.modules.interfaces import ITrayAction, IHostModule + +TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class TrayPublishAction(OpenPypeModule, ITrayAction): +class TrayPublishModule(OpenPypeModule, IHostModule, ITrayAction): label = "New Publish (beta)" name = "traypublish_tool" + host_name = "traypublish" def initialize(self, modules_settings): - import openpype self.enabled = True self.publish_paths = [ - os.path.join( - openpype.PACKAGE_DIR, - "hosts", - "traypublisher", - "plugins", - "publish" - ) + os.path.join(TRAYPUBLISH_ROOT_DIR, "plugins", "publish") ] self._experimental_tools = None @@ -29,7 +28,7 @@ class TrayPublishAction(OpenPypeModule, ITrayAction): self._experimental_tools = ExperimentalTools() def tray_menu(self, *args, **kwargs): - super(TrayPublishAction, self).tray_menu(*args, **kwargs) + super(TrayPublishModule, self).tray_menu(*args, **kwargs) traypublisher = self._experimental_tools.get("traypublisher") visible = False if traypublisher and traypublisher.enabled: @@ -45,5 +44,7 @@ class TrayPublishAction(OpenPypeModule, ITrayAction): self.publish_paths.extend(publish_paths) def run_traypublisher(self): - args = get_openpype_execute_args("traypublisher") + args = get_openpype_execute_args( + "module", "traypublish_tool", "launch" + ) run_detached_process(args) From 2a54de9b538ba4a29faaf3a82da043e276535877 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:07:52 +0200 Subject: [PATCH 074/101] added cli commands to traypublisher module --- openpype/hosts/traypublisher/module.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/hosts/traypublisher/module.py b/openpype/hosts/traypublisher/module.py index 25012900bc..6a088af635 100644 --- a/openpype/hosts/traypublisher/module.py +++ b/openpype/hosts/traypublisher/module.py @@ -48,3 +48,20 @@ class TrayPublishModule(OpenPypeModule, IHostModule, ITrayAction): "module", "traypublish_tool", "launch" ) run_detached_process(args) + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group(TrayPublishModule.name, help="TrayPublisher related commands.") +def cli_main(): + pass + + +@cli_main.command() +def launch(): + """Launch TrayPublish tool UI.""" + + from openpype.tools import traypublisher + + traypublisher.main() From e7000f0108d3a49934b1e7e3e5c20d2d63ffa7f2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:08:03 +0200 Subject: [PATCH 075/101] removed global traypublisher cli command --- openpype/cli.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/cli.py b/openpype/cli.py index ffe288040e..4b653ac43c 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -46,12 +46,6 @@ def standalonepublisher(): PypeCommands().launch_standalone_publisher() -@main.command() -def traypublisher(): - """Show new OpenPype Standalone publisher UI.""" - PypeCommands().launch_traypublisher() - - @main.command() def tray(): """Launch pype tray. From f9c49fcaefd04dbb5661f22193224456cf97da88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:12:06 +0200 Subject: [PATCH 076/101] better fill of module name --- openpype/hosts/traypublisher/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/module.py b/openpype/hosts/traypublisher/module.py index 6a088af635..92a2312fec 100644 --- a/openpype/hosts/traypublisher/module.py +++ b/openpype/hosts/traypublisher/module.py @@ -45,7 +45,7 @@ class TrayPublishModule(OpenPypeModule, IHostModule, ITrayAction): def run_traypublisher(self): args = get_openpype_execute_args( - "module", "traypublish_tool", "launch" + "module", self.name, "launch" ) run_detached_process(args) From ffaa0b7adf6816e12500506f5d77ff4ab1ade3f8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:22:24 +0200 Subject: [PATCH 077/101] moved standalonepublish action into standalone publish host --- openpype/hosts/standalonepublisher/__init__.py | 6 ++++++ .../standalonepublisher/standalonepublish_module.py} | 0 2 files changed, 6 insertions(+) rename openpype/{modules/standalonepublish_action.py => hosts/standalonepublisher/standalonepublish_module.py} (100%) diff --git a/openpype/hosts/standalonepublisher/__init__.py b/openpype/hosts/standalonepublisher/__init__.py index e69de29bb2..64c6d995f7 100644 --- a/openpype/hosts/standalonepublisher/__init__.py +++ b/openpype/hosts/standalonepublisher/__init__.py @@ -0,0 +1,6 @@ +from standalonepublish_module import StandAlonePublishModule + + +__all__ = ( + "StandAlonePublishModule", +) diff --git a/openpype/modules/standalonepublish_action.py b/openpype/hosts/standalonepublisher/standalonepublish_module.py similarity index 100% rename from openpype/modules/standalonepublish_action.py rename to openpype/hosts/standalonepublisher/standalonepublish_module.py From 8c849670872d1d0be4ad5611e7c21c4d77fff8e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:23:20 +0200 Subject: [PATCH 078/101] modified standalone publish action to work also as host module --- .../standalonepublish_module.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/standalonepublisher/standalonepublish_module.py b/openpype/hosts/standalonepublisher/standalonepublish_module.py index ba53ce9b9e..2cd46ce342 100644 --- a/openpype/hosts/standalonepublisher/standalonepublish_module.py +++ b/openpype/hosts/standalonepublisher/standalonepublish_module.py @@ -1,26 +1,26 @@ import os import platform import subprocess + +import click + from openpype.lib import get_openpype_execute_args +from openpype.lib.execute import run_detached_process from openpype.modules import OpenPypeModule -from openpype_interfaces import ITrayAction +from openpype.modules.interfaces import ITrayAction, IHostModule + +STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -class StandAlonePublishAction(OpenPypeModule, ITrayAction): +class StandAlonePublishModule(OpenPypeModule, ITrayAction, IHostModule): label = "Publish" name = "standalonepublish_tool" + host_name = "standalonepublisher" def initialize(self, modules_settings): - import openpype self.enabled = modules_settings[self.name]["enabled"] self.publish_paths = [ - os.path.join( - openpype.PACKAGE_DIR, - "hosts", - "standalonepublisher", - "plugins", - "publish" - ) + os.path.join(STANDALONEPUBLISH_ROOT_DIR, "plugins", "publish") ] def tray_init(self): @@ -31,19 +31,10 @@ class StandAlonePublishAction(OpenPypeModule, ITrayAction): def connect_with_modules(self, enabled_modules): """Collect publish paths from other modules.""" + publish_paths = self.manager.collect_plugin_paths()["publish"] self.publish_paths.extend(publish_paths) def run_standalone_publisher(self): - args = get_openpype_execute_args("standalonepublisher") - kwargs = {} - if platform.system().lower() == "darwin": - new_args = ["open", "-na", args.pop(0), "--args"] - new_args.extend(args) - args = new_args - - detached_process = getattr(subprocess, "DETACHED_PROCESS", None) - if detached_process is not None: - kwargs["creationflags"] = detached_process - - subprocess.Popen(args, **kwargs) + args = get_openpype_execute_args("module", self.name, "launch") + run_detached_process(args) From 9db2eb3cc0d85d706e5637ae19bfc4ae6f49f028 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:23:32 +0200 Subject: [PATCH 079/101] added cli commands for standalone publisher --- .../standalonepublish_module.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/hosts/standalonepublisher/standalonepublish_module.py b/openpype/hosts/standalonepublisher/standalonepublish_module.py index 2cd46ce342..2d0114dee1 100644 --- a/openpype/hosts/standalonepublisher/standalonepublish_module.py +++ b/openpype/hosts/standalonepublisher/standalonepublish_module.py @@ -38,3 +38,22 @@ class StandAlonePublishModule(OpenPypeModule, ITrayAction, IHostModule): def run_standalone_publisher(self): args = get_openpype_execute_args("module", self.name, "launch") run_detached_process(args) + + def cli(self, click_group): + click_group.add_command(cli_main) + + +@click.group( + StandAlonePublishModule.name, + help="StandalonePublisher related commands.") +def cli_main(): + pass + + +@cli_main.command() +def launch(): + """Launch StandalonePublisher tool UI.""" + + from openpype.tools import standalonepublish + + standalonepublish.main() From 6a271aae101d86e33eaffe6571b736d8b0ab8c88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:25:10 +0200 Subject: [PATCH 080/101] removed standalonepublisher from global cli commands --- openpype/cli.py | 6 ------ openpype/pype_commands.py | 5 ----- website/docs/admin_openpype_commands.md | 7 ------- 3 files changed, 18 deletions(-) diff --git a/openpype/cli.py b/openpype/cli.py index 4b653ac43c..398d1a94c0 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -40,12 +40,6 @@ def settings(dev): PypeCommands().launch_settings_gui(dev) -@main.command() -def standalonepublisher(): - """Show Pype Standalone publisher UI.""" - PypeCommands().launch_standalone_publisher() - - @main.command() def tray(): """Launch pype tray. diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index a447aa916b..66bf5e9bb4 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -76,11 +76,6 @@ class PypeCommands: import (run_webserver) return run_webserver(*args, **kwargs) - @staticmethod - def launch_standalone_publisher(): - from openpype.tools import standalonepublish - standalonepublish.main() - @staticmethod def launch_traypublisher(): from openpype.tools import traypublisher diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md index 53fc12410f..8345398e1d 100644 --- a/website/docs/admin_openpype_commands.md +++ b/website/docs/admin_openpype_commands.md @@ -48,7 +48,6 @@ For more information [see here](admin_use.md#run-openpype). | interactive | Start python like interactive console session. | | | projectmanager | Launch Project Manager UI | [📑](#projectmanager-arguments) | | settings | Open Settings UI | [📑](#settings-arguments) | -| standalonepublisher | Open Standalone Publisher UI | [📑](#standalonepublisher-arguments) | --- ### `tray` arguments {#tray-arguments} @@ -159,12 +158,6 @@ openpypeconsole settings ``` --- -### `standalonepublisher` arguments {#standalonepublisher-arguments} -`standalonepublisher` has no command-line arguments. -```shell -openpype_console standalonepublisher -``` - ### `repack-version` arguments {#repack-version-arguments} Takes path to unzipped and possibly modified OpenPype version. Files will be zipped, checksums recalculated and version will be determined by folder name From 986e4325c2379001bf33ca3729e8e9608ce9fe87 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:26:01 +0200 Subject: [PATCH 081/101] fix import --- openpype/hosts/standalonepublisher/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/__init__.py b/openpype/hosts/standalonepublisher/__init__.py index 64c6d995f7..394d5be397 100644 --- a/openpype/hosts/standalonepublisher/__init__.py +++ b/openpype/hosts/standalonepublisher/__init__.py @@ -1,4 +1,4 @@ -from standalonepublish_module import StandAlonePublishModule +from .standalonepublish_module import StandAlonePublishModule __all__ = ( From ab1e6c4e3dddacb0726c4eddea078af8fca42ebd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:30:27 +0200 Subject: [PATCH 082/101] removed unused imports --- openpype/hosts/standalonepublisher/standalonepublish_module.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/standalonepublisher/standalonepublish_module.py b/openpype/hosts/standalonepublisher/standalonepublish_module.py index 2d0114dee1..bf8e1d2c23 100644 --- a/openpype/hosts/standalonepublisher/standalonepublish_module.py +++ b/openpype/hosts/standalonepublisher/standalonepublish_module.py @@ -1,6 +1,4 @@ import os -import platform -import subprocess import click From 227a21c057412ee764facce5ba0ebec6bf19630c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:33:52 +0200 Subject: [PATCH 083/101] removed uninstall function --- openpype/hosts/tvpaint/api/__init__.py | 2 -- openpype/hosts/tvpaint/api/pipeline.py | 13 ------------- 2 files changed, 15 deletions(-) diff --git a/openpype/hosts/tvpaint/api/__init__.py b/openpype/hosts/tvpaint/api/__init__.py index c461b33f4b..43d411d8f9 100644 --- a/openpype/hosts/tvpaint/api/__init__.py +++ b/openpype/hosts/tvpaint/api/__init__.py @@ -6,7 +6,6 @@ from . import pipeline from . import plugin from .pipeline import ( install, - uninstall, maintained_selection, remove_instance, list_instances, @@ -33,7 +32,6 @@ __all__ = ( "plugin", "install", - "uninstall", "maintained_selection", "remove_instance", "list_instances", diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 0118c0104b..73e2c2335c 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -91,19 +91,6 @@ def install(): register_event_callback("application.exit", application_exit) -def uninstall(): - """Uninstall TVPaint-specific functionality. - - This function is called automatically on calling `uninstall_host()`. - """ - - log.info("OpenPype - Uninstalling TVPaint integration") - pyblish.api.deregister_host("tvpaint") - pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugin_path(LOAD_PATH) - deregister_creator_plugin_path(CREATE_PATH) - - def containerise( name, namespace, members, context, loader, current_containers=None ): From b9c175d9691bc07f3bc5db9f31604363edf7f969 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:39:01 +0200 Subject: [PATCH 084/101] converted tvpaint into module --- openpype/hosts/tvpaint/__init__.py | 26 +++++---------- openpype/hosts/tvpaint/tvpaint_module.py | 42 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 openpype/hosts/tvpaint/tvpaint_module.py diff --git a/openpype/hosts/tvpaint/__init__.py b/openpype/hosts/tvpaint/__init__.py index 09b7c52cd1..068631a010 100644 --- a/openpype/hosts/tvpaint/__init__.py +++ b/openpype/hosts/tvpaint/__init__.py @@ -1,20 +1,10 @@ -import os +from .tvpaint_module import ( + get_launch_script_path, + TVPaintModule, +) -def add_implementation_envs(env, _app): - """Modify environments to contain all required for implementation.""" - defaults = { - "OPENPYPE_LOG_NO_COLORS": "True" - } - for key, value in defaults.items(): - if not env.get(key): - env[key] = value - - -def get_launch_script_path(): - current_dir = os.path.dirname(os.path.abspath(__file__)) - return os.path.join( - current_dir, - "api", - "launch_script.py" - ) +__all__ = ( + "get_launch_script_path", + "TVPaintModule", +) diff --git a/openpype/hosts/tvpaint/tvpaint_module.py b/openpype/hosts/tvpaint/tvpaint_module.py new file mode 100644 index 0000000000..a2471553a6 --- /dev/null +++ b/openpype/hosts/tvpaint/tvpaint_module.py @@ -0,0 +1,42 @@ +import os +from openpype.modules import OpenPypeModule +from openpype.modules.interfaces import IHostModule + +TVPAINT_ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def get_launch_script_path(): + return os.path.join( + TVPAINT_ROOT_DIR, + "api", + "launch_script.py" + ) + + + +class TVPaintModule(OpenPypeModule, IHostModule): + name = "tvpaint" + host_name = "tvpaint" + + def initialize(self, module_settings): + self.enabled = True + + def add_implementation_envs(env, _app): + """Modify environments to contain all required for implementation.""" + + defaults = { + "OPENPYPE_LOG_NO_COLORS": "True" + } + for key, value in defaults.items(): + if not env.get(key): + env[key] = value + + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(TVPAINT_ROOT_DIR, "hooks") + ] + + def get_workfile_extensions(self): + return [".tvpp"] From c6f8b4559d249655f2b003982b8cf07f71fc70cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 22 Aug 2022 18:45:05 +0200 Subject: [PATCH 085/101] import TVPAINT_ROOT_DIR in init --- openpype/hosts/tvpaint/__init__.py | 2 ++ openpype/hosts/tvpaint/tvpaint_module.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/__init__.py b/openpype/hosts/tvpaint/__init__.py index 068631a010..0a84b575dc 100644 --- a/openpype/hosts/tvpaint/__init__.py +++ b/openpype/hosts/tvpaint/__init__.py @@ -1,10 +1,12 @@ from .tvpaint_module import ( get_launch_script_path, TVPaintModule, + TVPAINT_ROOT_DIR, ) __all__ = ( "get_launch_script_path", "TVPaintModule", + "TVPAINT_ROOT_DIR", ) diff --git a/openpype/hosts/tvpaint/tvpaint_module.py b/openpype/hosts/tvpaint/tvpaint_module.py index a2471553a6..c29602babc 100644 --- a/openpype/hosts/tvpaint/tvpaint_module.py +++ b/openpype/hosts/tvpaint/tvpaint_module.py @@ -13,7 +13,6 @@ def get_launch_script_path(): ) - class TVPaintModule(OpenPypeModule, IHostModule): name = "tvpaint" host_name = "tvpaint" From d65607eedbc2a80121820cc8b6efc66cbc759006 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:27:26 +0200 Subject: [PATCH 086/101] removed unused imports --- openpype/hosts/tvpaint/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py index 73e2c2335c..427c927264 100644 --- a/openpype/hosts/tvpaint/api/pipeline.py +++ b/openpype/hosts/tvpaint/api/pipeline.py @@ -16,8 +16,6 @@ from openpype.pipeline import ( legacy_io, register_loader_plugin_path, register_creator_plugin_path, - deregister_loader_plugin_path, - deregister_creator_plugin_path, AVALON_CONTAINER_ID, ) From 5bf34ebed7c4c848681c484c50ec70bd4ebb728d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:29:27 +0200 Subject: [PATCH 087/101] fix project overrides save to mongo --- openpype/settings/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 09f36aa16e..79ec6248ac 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -957,7 +957,7 @@ class MongoSettingsHandler(SettingsHandler): if project_settings_doc: self.collection.update_one( {"_id": project_settings_doc["_id"]}, - new_project_settings_doc + {"$set": new_project_settings_doc} ) else: self.collection.insert_one(new_project_settings_doc) From 3176a0130d6b3ab110c0fd3c3616c6c56172df5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 10:40:27 +0200 Subject: [PATCH 088/101] replaced changelog and upgrade notes with releases information --- website/docs/admin_releases.md | 9 + website/docs/changelog.md | 1138 -------------------------------- website/docs/upgrade_notes.md | 165 ----- website/sidebars.js | 6 +- 4 files changed, 10 insertions(+), 1308 deletions(-) create mode 100644 website/docs/admin_releases.md delete mode 100644 website/docs/changelog.md delete mode 100644 website/docs/upgrade_notes.md diff --git a/website/docs/admin_releases.md b/website/docs/admin_releases.md new file mode 100644 index 0000000000..bba5a22110 --- /dev/null +++ b/website/docs/admin_releases.md @@ -0,0 +1,9 @@ +--- +id: admin_releases +title: Releases +sidebar_label: Releases +--- + +Information about releases can be found on GitHub [Releases page](https://github.com/pypeclub/OpenPype/releases). + +You can find features and bugfixes in the codebase or full changelog for advanced users. diff --git a/website/docs/changelog.md b/website/docs/changelog.md deleted file mode 100644 index 448592b930..0000000000 --- a/website/docs/changelog.md +++ /dev/null @@ -1,1138 +0,0 @@ ---- -id: changelog -title: Changelog -sidebar_label: Changelog ---- - -## [2.18.0](https://github.com/pypeclub/openpype/tree/2.18.0) -_**release date:** (2021-05-18)_ - -[Full Changelog](https://github.com/pypeclub/openpype/compare/2.17.3...2.18.0) - -**Enhancements:** - -- Use SubsetLoader and multiple contexts for delete_old_versions [\#1484](ttps://github.com/pypeclub/OpenPype/pull/1484)) -- TVPaint: Increment workfile version on successful publish. [\#1489](https://github.com/pypeclub/OpenPype/pull/1489) -- Maya: Use of multiple deadline servers [\#1483](https://github.com/pypeclub/OpenPype/pull/1483) - -**Fixed bugs:** - -- Use instance frame start instead of timeline. [\#1486](https://github.com/pypeclub/OpenPype/pull/1486) -- Maya: Redshift - set proper start frame on proxy [\#1480](https://github.com/pypeclub/OpenPype/pull/1480) -- Maya: wrong collection of playblasted frames [\#1517](https://github.com/pypeclub/OpenPype/pull/1517) -- Existing subsets hints in creator [\#1502](https://github.com/pypeclub/OpenPype/pull/1502) - - -### [2.17.3](https://github.com/pypeclub/openpype/tree/2.17.3) -_**release date:** (2021-05-06)_ - -[Full Changelog](https://github.com/pypeclub/openpype/compare/CI/3.0.0-rc.3...2.17.3) - -**Fixed bugs:** - -- Nuke: workfile version synced to db version always [\#1479](https://github.com/pypeclub/OpenPype/pull/1479) - -### [2.17.2](https://github.com/pypeclub/openpype/tree/2.17.2) -_**release date:** (2021-05-04)_ - -[Full Changelog](https://github.com/pypeclub/openpype/compare/CI/3.0.0-rc.1...2.17.2) - -**Enhancements:** - -- Forward/Backward compatible apps and tools with OpenPype 3 [\#1463](https://github.com/pypeclub/OpenPype/pull/1463) - -### [2.17.1](https://github.com/pypeclub/openpype/tree/2.17.1) -_**release date:** (2021-04-30)_ - -[Full Changelog](https://github.com/pypeclub/openpype/compare/2.17.0...2.17.1) - -**Enhancements:** - -- Faster settings UI loading [\#1442](https://github.com/pypeclub/OpenPype/pull/1442) -- Nuke: deadline submission with gpu [\#1414](https://github.com/pypeclub/OpenPype/pull/1414) -- TVPaint frame range definition [\#1424](https://github.com/pypeclub/OpenPype/pull/1424) -- PS - group all published instances [\#1415](https://github.com/pypeclub/OpenPype/pull/1415) -- Add task name to context pop up. [\#1383](https://github.com/pypeclub/OpenPype/pull/1383) -- Enhance review letterbox feature. [\#1371](https://github.com/pypeclub/OpenPype/pull/1371) -- AE add duration validation [\#1363](https://github.com/pypeclub/OpenPype/pull/1363) - -**Fixed bugs:** - -- Houdini menu filename [\#1417](https://github.com/pypeclub/OpenPype/pull/1417) -- Nuke: fixing undo for loaded mov and sequence [\#1433](https://github.com/pypeclub/OpenPype/pull/1433) -- AE - validation for duration was 1 frame shorter [\#1426](https://github.com/pypeclub/OpenPype/pull/1426) - -**Merged pull requests:** - -- Maya: Vray - problem getting all file nodes for look publishing [\#1399](https://github.com/pypeclub/OpenPype/pull/1399) -- Maya: Support for Redshift proxies [\#1360](https://github.com/pypeclub/OpenPype/pull/1360) - -## [2.17.0](https://github.com/pypeclub/openpype/tree/2.17.0) -_**release date:** (2021-04-20)_ - -[Full Changelog](https://github.com/pypeclub/openpype/compare/CI/3.0.0-beta.2...2.17.0) - -**Enhancements:** - -- Forward compatible ftrack group [\#1243](https://github.com/pypeclub/OpenPype/pull/1243) -- Maya: Make tx option configurable with presets [\#1328](https://github.com/pypeclub/OpenPype/pull/1328) -- TVPaint asset name validation [\#1302](https://github.com/pypeclub/OpenPype/pull/1302) -- TV Paint: Set initial project settings. [\#1299](https://github.com/pypeclub/OpenPype/pull/1299) -- TV Paint: Validate mark in and out. [\#1298](https://github.com/pypeclub/OpenPype/pull/1298) -- Validate project settings [\#1297](https://github.com/pypeclub/OpenPype/pull/1297) -- After Effects: added SubsetManager [\#1234](https://github.com/pypeclub/OpenPype/pull/1234) -- Show error message in pyblish UI [\#1206](https://github.com/pypeclub/OpenPype/pull/1206) - -**Fixed bugs:** - -- Hiero: fixing source frame from correct object [\#1362](https://github.com/pypeclub/OpenPype/pull/1362) -- Nuke: fix colourspace, prerenders and nuke panes opening [\#1308](https://github.com/pypeclub/OpenPype/pull/1308) -- AE remove orphaned instance from workfile - fix self.stub [\#1282](https://github.com/pypeclub/OpenPype/pull/1282) -- Nuke: deadline submission with search replaced env values from preset [\#1194](https://github.com/pypeclub/OpenPype/pull/1194) -- Ftrack custom attributes in bulks [\#1312](https://github.com/pypeclub/OpenPype/pull/1312) -- Ftrack optional pypclub role [\#1303](https://github.com/pypeclub/OpenPype/pull/1303) -- After Effects: remove orphaned instances [\#1275](https://github.com/pypeclub/OpenPype/pull/1275) -- Avalon schema names [\#1242](https://github.com/pypeclub/OpenPype/pull/1242) -- Handle duplication of Task name [\#1226](https://github.com/pypeclub/OpenPype/pull/1226) -- Modified path of plugin loads for Harmony and TVPaint [\#1217](https://github.com/pypeclub/OpenPype/pull/1217) -- Regex checks in profiles filtering [\#1214](https://github.com/pypeclub/OpenPype/pull/1214) -- Update custom ftrack session attributes [\#1202](https://github.com/pypeclub/OpenPype/pull/1202) -- Nuke: write node colorspace ignore `default\(\)` label [\#1199](https://github.com/pypeclub/OpenPype/pull/1199) - -## [2.16.0](https://github.com/pypeclub/pype/tree/2.16.0) - - _**release date:** 2021-03-22_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.15.3...2.16.0) - -**Enhancements:** - -- Nuke: deadline submit limit group filter [\#1167](https://github.com/pypeclub/pype/pull/1167) -- Maya: support for Deadline Group and Limit Groups - backport 2.x [\#1156](https://github.com/pypeclub/pype/pull/1156) -- Maya: fixes for Redshift support [\#1152](https://github.com/pypeclub/pype/pull/1152) -- Nuke: adding preset for a Read node name to all img and mov Loaders [\#1146](https://github.com/pypeclub/pype/pull/1146) -- nuke deadline submit with environ var from presets overrides [\#1142](https://github.com/pypeclub/pype/pull/1142) -- Change timers after task change [\#1138](https://github.com/pypeclub/pype/pull/1138) -- Nuke: shortcuts for Pype menu [\#1127](https://github.com/pypeclub/pype/pull/1127) -- Nuke: workfile template [\#1124](https://github.com/pypeclub/pype/pull/1124) -- Sites local settings by site name [\#1117](https://github.com/pypeclub/pype/pull/1117) -- Reset loader's asset selection on context change [\#1106](https://github.com/pypeclub/pype/pull/1106) -- Bulk mov render publishing [\#1101](https://github.com/pypeclub/pype/pull/1101) -- Photoshop: mark publishable instances [\#1093](https://github.com/pypeclub/pype/pull/1093) -- Added ability to define BG color for extract review [\#1088](https://github.com/pypeclub/pype/pull/1088) -- TVPaint extractor enhancement [\#1080](https://github.com/pypeclub/pype/pull/1080) -- Photoshop: added support for .psb in workfiles [\#1078](https://github.com/pypeclub/pype/pull/1078) -- Optionally add task to subset name [\#1072](https://github.com/pypeclub/pype/pull/1072) -- Only extend clip range when collecting. [\#1008](https://github.com/pypeclub/pype/pull/1008) -- Collect audio for farm reviews. [\#1073](https://github.com/pypeclub/pype/pull/1073) - - -**Fixed bugs:** - -- Fix path spaces in jpeg extractor [\#1174](https://github.com/pypeclub/pype/pull/1174) -- Maya: Bugfix: superclass for CreateCameraRig [\#1166](https://github.com/pypeclub/pype/pull/1166) -- Maya: Submit to Deadline - fix typo in condition [\#1163](https://github.com/pypeclub/pype/pull/1163) -- Avoid dot in repre extension [\#1125](https://github.com/pypeclub/pype/pull/1125) -- Fix versions variable usage in standalone publisher [\#1090](https://github.com/pypeclub/pype/pull/1090) -- Collect instance data fix subset query [\#1082](https://github.com/pypeclub/pype/pull/1082) -- Fix getting the camera name. [\#1067](https://github.com/pypeclub/pype/pull/1067) -- Nuke: Ensure "NUKE\_TEMP\_DIR" is not part of the Deadline job environment. [\#1064](https://github.com/pypeclub/pype/pull/1064) - -### [2.15.3](https://github.com/pypeclub/pype/tree/2.15.3) - - _**release date:** 2021-02-26_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.15.2...2.15.3) - -**Enhancements:** - -- Maya: speedup renderable camera collection [\#1053](https://github.com/pypeclub/pype/pull/1053) -- Harmony - add regex search to filter allowed task names for collectin… [\#1047](https://github.com/pypeclub/pype/pull/1047) - -**Fixed bugs:** - -- Ftrack integrate hierarchy fix [\#1085](https://github.com/pypeclub/pype/pull/1085) -- Explicit subset filter in anatomy instance data [\#1059](https://github.com/pypeclub/pype/pull/1059) -- TVPaint frame offset [\#1057](https://github.com/pypeclub/pype/pull/1057) -- Auto fix unicode strings [\#1046](https://github.com/pypeclub/pype/pull/1046) - -### [2.15.2](https://github.com/pypeclub/pype/tree/2.15.2) - - _**release date:** 2021-02-19_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.15.1...2.15.2) - -**Enhancements:** - -- Maya: Vray scene publishing [\#1013](https://github.com/pypeclub/pype/pull/1013) - -**Fixed bugs:** - -- Fix entity move under project [\#1040](https://github.com/pypeclub/pype/pull/1040) -- smaller nuke fixes from production [\#1036](https://github.com/pypeclub/pype/pull/1036) -- TVPaint thumbnail extract fix [\#1031](https://github.com/pypeclub/pype/pull/1031) - -### [2.15.1](https://github.com/pypeclub/pype/tree/2.15.1) - - _**release date:** 2021-02-12_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.15.0...2.15.1) - -**Enhancements:** - -- Delete version as loader action [\#1011](https://github.com/pypeclub/pype/pull/1011) -- Delete old versions [\#445](https://github.com/pypeclub/pype/pull/445) - -**Fixed bugs:** - -- PS - remove obsolete functions from pywin32 [\#1006](https://github.com/pypeclub/pype/pull/1006) -- Clone description of review session objects. [\#922](https://github.com/pypeclub/pype/pull/922) - -## [2.15.0](https://github.com/pypeclub/pype/tree/2.15.0) - - _**release date:** 2021-02-09_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.6...2.15.0) - -**Enhancements:** - -- Resolve - loading and updating clips [\#932](https://github.com/pypeclub/pype/pull/932) -- Release/2.15.0 [\#926](https://github.com/pypeclub/pype/pull/926) -- Photoshop: add option for template.psd and prelaunch hook [\#894](https://github.com/pypeclub/pype/pull/894) -- Nuke: deadline presets [\#993](https://github.com/pypeclub/pype/pull/993) -- Maya: Alembic only set attributes that exists. [\#986](https://github.com/pypeclub/pype/pull/986) -- Harmony: render local and handle fixes [\#981](https://github.com/pypeclub/pype/pull/981) -- PSD Bulk export of ANIM group [\#965](https://github.com/pypeclub/pype/pull/965) -- AE - added prelaunch hook for opening last or workfile from template [\#944](https://github.com/pypeclub/pype/pull/944) -- PS - safer handling of loading of workfile [\#941](https://github.com/pypeclub/pype/pull/941) -- Maya: Handling Arnold referenced AOVs [\#938](https://github.com/pypeclub/pype/pull/938) -- TVPaint: switch layer IDs for layer names during identification [\#903](https://github.com/pypeclub/pype/pull/903) -- TVPaint audio/sound loader [\#893](https://github.com/pypeclub/pype/pull/893) -- Clone review session with children. [\#891](https://github.com/pypeclub/pype/pull/891) -- Simple compositing data packager for freelancers [\#884](https://github.com/pypeclub/pype/pull/884) -- Harmony deadline submission [\#881](https://github.com/pypeclub/pype/pull/881) -- Maya: Optionally hide image planes from reviews. [\#840](https://github.com/pypeclub/pype/pull/840) -- Maya: handle referenced AOVs for Vray [\#824](https://github.com/pypeclub/pype/pull/824) -- DWAA/DWAB support on windows [\#795](https://github.com/pypeclub/pype/pull/795) -- Unreal: animation, layout and setdress updates [\#695](https://github.com/pypeclub/pype/pull/695) - -**Fixed bugs:** - -- Maya: Looks - disable hardlinks [\#995](https://github.com/pypeclub/pype/pull/995) -- Fix Ftrack custom attribute update [\#982](https://github.com/pypeclub/pype/pull/982) -- Prores ks in burnin script [\#960](https://github.com/pypeclub/pype/pull/960) -- terminal.py crash on import [\#839](https://github.com/pypeclub/pype/pull/839) -- Extract review handle bizarre pixel aspect ratio [\#990](https://github.com/pypeclub/pype/pull/990) -- Nuke: add nuke related env var to sumbission [\#988](https://github.com/pypeclub/pype/pull/988) -- Nuke: missing preset's variable [\#984](https://github.com/pypeclub/pype/pull/984) -- Get creator by name fix [\#979](https://github.com/pypeclub/pype/pull/979) -- Fix update of project's tasks on Ftrack sync [\#972](https://github.com/pypeclub/pype/pull/972) -- nuke: wrong frame offset in mov loader [\#971](https://github.com/pypeclub/pype/pull/971) -- Create project structure action fix multiroot [\#967](https://github.com/pypeclub/pype/pull/967) -- PS: remove pywin installation from hook [\#964](https://github.com/pypeclub/pype/pull/964) -- Prores ks in burnin script [\#959](https://github.com/pypeclub/pype/pull/959) -- Subset family is now stored in subset document [\#956](https://github.com/pypeclub/pype/pull/956) -- DJV new version arguments [\#954](https://github.com/pypeclub/pype/pull/954) -- TV Paint: Fix single frame Sequence [\#953](https://github.com/pypeclub/pype/pull/953) -- nuke: missing `file` knob update [\#933](https://github.com/pypeclub/pype/pull/933) -- Photoshop: Create from single layer was failing [\#920](https://github.com/pypeclub/pype/pull/920) -- Nuke: baking mov with correct colorspace inherited from write [\#909](https://github.com/pypeclub/pype/pull/909) -- Launcher fix actions discover [\#896](https://github.com/pypeclub/pype/pull/896) -- Get the correct file path for the updated mov. [\#889](https://github.com/pypeclub/pype/pull/889) -- Maya: Deadline submitter - shared data access violation [\#831](https://github.com/pypeclub/pype/pull/831) -- Maya: Take into account vray master AOV switch [\#822](https://github.com/pypeclub/pype/pull/822) - -**Merged pull requests:** - -- Refactor blender to 3.0 format [\#934](https://github.com/pypeclub/pype/pull/934) - -### [2.14.6](https://github.com/pypeclub/pype/tree/2.14.6) - - _**release date:** 2021-01-15_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.5...2.14.6) - -**Fixed bugs:** - -- Nuke: improving of hashing path [\#885](https://github.com/pypeclub/pype/pull/885) - -**Merged pull requests:** - -- Hiero: cut videos with correct secons [\#892](https://github.com/pypeclub/pype/pull/892) -- Faster sync to avalon preparation [\#869](https://github.com/pypeclub/pype/pull/869) - -### [2.14.5](https://github.com/pypeclub/pype/tree/2.14.5) - - _**release date:** 2021-01-06_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.4...2.14.5) - -**Merged pull requests:** - -- Pype logger refactor [\#866](https://github.com/pypeclub/pype/pull/866) - -### [2.14.4](https://github.com/pypeclub/pype/tree/2.14.4) - - _**release date:** 2020-12-18_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.3...2.14.4) - -**Merged pull requests:** - -- Fix - AE - added explicit cast to int [\#837](https://github.com/pypeclub/pype/pull/837) - -### [2.14.3](https://github.com/pypeclub/pype/tree/2.14.3) - - _**release date:** 2020-12-16_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.2...2.14.3) - -**Fixed bugs:** - -- TVPaint repair invalid metadata [\#809](https://github.com/pypeclub/pype/pull/809) -- Feature/push hier value to nonhier action [\#807](https://github.com/pypeclub/pype/pull/807) -- Harmony: fix palette and image sequence loader [\#806](https://github.com/pypeclub/pype/pull/806) - -**Merged pull requests:** - -- respecting space in path [\#823](https://github.com/pypeclub/pype/pull/823) - -### [2.14.2](https://github.com/pypeclub/pype/tree/2.14.2) - - _**release date:** 2020-12-04_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.1...2.14.2) - -**Enhancements:** - -- Collapsible wrapper in settings [\#767](https://github.com/pypeclub/pype/pull/767) - -**Fixed bugs:** - -- Harmony: template extraction and palettes thumbnails on mac [\#768](https://github.com/pypeclub/pype/pull/768) -- TVPaint store context to workfile metadata \(764\) [\#766](https://github.com/pypeclub/pype/pull/766) -- Extract review audio cut fix [\#763](https://github.com/pypeclub/pype/pull/763) - -**Merged pull requests:** - -- AE: fix publish after background load [\#781](https://github.com/pypeclub/pype/pull/781) -- TVPaint store members key [\#769](https://github.com/pypeclub/pype/pull/769) - -### [2.14.1](https://github.com/pypeclub/pype/tree/2.14.1) - - _**release date:** 2020-11-27_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.14.0...2.14.1) - -**Enhancements:** - -- Settings required keys in modifiable dict [\#770](https://github.com/pypeclub/pype/pull/770) -- Extract review may not add audio to output [\#761](https://github.com/pypeclub/pype/pull/761) - -**Fixed bugs:** - -- After Effects: frame range, file format and render source scene fixes [\#760](https://github.com/pypeclub/pype/pull/760) -- Hiero: trimming review with clip event number [\#754](https://github.com/pypeclub/pype/pull/754) -- TVPaint: fix updating of loaded subsets [\#752](https://github.com/pypeclub/pype/pull/752) -- Maya: Vray handling of default aov [\#748](https://github.com/pypeclub/pype/pull/748) -- Maya: multiple renderable cameras in layer didn't work [\#744](https://github.com/pypeclub/pype/pull/744) -- Ftrack integrate custom attributes fix [\#742](https://github.com/pypeclub/pype/pull/742) - - - -## [2.14.0](https://github.com/pypeclub/pype/tree/2.14.0) - - _**release date:** 2020-11-24_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.7...2.14.0) - -**Enhancements:** - -- Ftrack: Event for syncing shot or asset status with tasks.[\#736](https://github.com/pypeclub/pype/pull/736) -- Maya: add camera rig publishing option [\#721](https://github.com/pypeclub/pype/pull/721) -- Maya: Ask user to select non-default camera from scene or create a new. [\#678](https://github.com/pypeclub/pype/pull/678) -- Maya: Camera name can be added to burnins. [\#674](https://github.com/pypeclub/pype/pull/674) -- Sort instances by label in pyblish gui [\#719](https://github.com/pypeclub/pype/pull/719) -- Synchronize ftrack hierarchical and shot attributes [\#716](https://github.com/pypeclub/pype/pull/716) -- Standalone Publisher: Publish editorial from separate image sequences [\#699](https://github.com/pypeclub/pype/pull/699) -- Render publish plugins abstraction [\#687](https://github.com/pypeclub/pype/pull/687) -- TV Paint: image loader with options [\#675](https://github.com/pypeclub/pype/pull/675) -- **TV Paint (Beta):** initial implementation of creators and local rendering [\#693](https://github.com/pypeclub/pype/pull/693) -- **After Effects (Beta):** base integration with loaders [\#667](https://github.com/pypeclub/pype/pull/667) -- Harmony: Javascript refactoring and overall stability improvements [\#666](https://github.com/pypeclub/pype/pull/666) - -**Fixed bugs:** - -- TVPaint: extract review fix [\#740](https://github.com/pypeclub/pype/pull/740) -- After Effects: Review were not being sent to ftrack [\#738](https://github.com/pypeclub/pype/pull/738) -- Maya: vray proxy was not loading [\#722](https://github.com/pypeclub/pype/pull/722) -- Maya: Vray expected file fixes [\#682](https://github.com/pypeclub/pype/pull/682) - -**Deprecated:** - -- Removed artist view from pyblish gui [\#717](https://github.com/pypeclub/pype/pull/717) -- Maya: disable legacy override check for cameras [\#715](https://github.com/pypeclub/pype/pull/715) - - - - -### [2.13.7](https://github.com/pypeclub/pype/tree/2.13.7) - - _**release date:** 2020-11-19_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.6...2.13.7) - -**Merged pull requests:** - -- fix\(SP\): getting fps from context instead of nonexistent entity [\#729](https://github.com/pypeclub/pype/pull/729) - - - - -### [2.13.6](https://github.com/pypeclub/pype/tree/2.13.6) - - _**release date:** 2020-11-15_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.5...2.13.6) - -**Fixed bugs:** - -- Maya workfile version wasn't syncing with renders properly [\#711](https://github.com/pypeclub/pype/pull/711) -- Maya: Fix for publishing multiple cameras with review from the same scene [\#710](https://github.com/pypeclub/pype/pull/710) - - - - -### [2.13.5](https://github.com/pypeclub/pype/tree/2.13.5) - - _**release date:** 2020-11-12_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.4...2.13.5) - - -**Fixed bugs:** - -- Wrong thumbnail file was picked when publishing sequence in standalone publisher [\#703](https://github.com/pypeclub/pype/pull/703) -- Fix: Burnin data pass and FFmpeg tool check [\#701](https://github.com/pypeclub/pype/pull/701) - - - - -### [2.13.4](https://github.com/pypeclub/pype/tree/2.13.4) - - _**release date:** 2020-11-09_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.3...2.13.4) - - -**Fixed bugs:** - -- Photoshop unhiding hidden layers [\#688](https://github.com/pypeclub/pype/issues/688) -- Nuke: Favorite directories "shot dir" "project dir" - not working \#684 [\#685](https://github.com/pypeclub/pype/pull/685) - - - - - -### [2.13.3](https://github.com/pypeclub/pype/tree/2.13.3) - - _**release date:** _2020-11-03_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.2...2.13.3) - -**Fixed bugs:** - -- Fix ffmpeg executable path with spaces [\#680](https://github.com/pypeclub/pype/pull/680) -- Hotfix: Added default version number [\#679](https://github.com/pypeclub/pype/pull/679) - - - - -### [2.13.2](https://github.com/pypeclub/pype/tree/2.13.2) - - _**release date:** 2020-10-28_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.1...2.13.2) - -**Fixed bugs:** - -- Nuke: wrong conditions when fixing legacy write nodes [\#665](https://github.com/pypeclub/pype/pull/665) - - - - -### [2.13.1](https://github.com/pypeclub/pype/tree/2.13.1) - - _**release date:** 2020-10-23_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.13.0...2.13.1) - -**Fixed bugs:** - -- Photoshop: Layer name is not propagating to metadata [\#654](https://github.com/pypeclub/pype/issues/654) -- Photoshop: Loader in fails with "can't set attribute" [\#650](https://github.com/pypeclub/pype/issues/650) -- Hiero: Review video file adding one frame to the end [\#659](https://github.com/pypeclub/pype/issues/659) - - - -## [2.13.0](https://github.com/pypeclub/pype/tree/2.13.0) - - _**release date:** 2020-10-16_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.5...2.13.0) - -**Enhancements:** - -- Deadline Output Folder [\#636](https://github.com/pypeclub/pype/issues/636) -- Nuke Camera Loader [\#565](https://github.com/pypeclub/pype/issues/565) -- Deadline publish job shows publishing output folder [\#649](https://github.com/pypeclub/pype/pull/649) -- Get latest version in lib [\#642](https://github.com/pypeclub/pype/pull/642) -- Improved publishing of multiple representation from SP [\#638](https://github.com/pypeclub/pype/pull/638) -- TvPaint: launch shot work file from within Ftrack [\#631](https://github.com/pypeclub/pype/pull/631) -- Add mp4 support for RV action. [\#628](https://github.com/pypeclub/pype/pull/628) -- Maya: allow renders to have version synced with workfile [\#618](https://github.com/pypeclub/pype/pull/618) -- Renaming nukestudio host folder to hiero [\#617](https://github.com/pypeclub/pype/pull/617) -- Harmony: More efficient publishing [\#615](https://github.com/pypeclub/pype/pull/615) -- Ftrack server action improvement [\#608](https://github.com/pypeclub/pype/pull/608) -- Deadline user defaults to pype username if present [\#607](https://github.com/pypeclub/pype/pull/607) -- Standalone publisher now has icon [\#606](https://github.com/pypeclub/pype/pull/606) -- Nuke render write targeting knob improvement [\#603](https://github.com/pypeclub/pype/pull/603) -- Animated pyblish gui [\#602](https://github.com/pypeclub/pype/pull/602) -- Maya: Deadline - make use of asset dependencies optional [\#591](https://github.com/pypeclub/pype/pull/591) -- Nuke: Publishing, loading and updating alembic cameras [\#575](https://github.com/pypeclub/pype/pull/575) -- Maya: add look assigner to pype menu even if scriptsmenu is not available [\#573](https://github.com/pypeclub/pype/pull/573) -- Store task types in the database [\#572](https://github.com/pypeclub/pype/pull/572) -- Maya: Tiled EXRs to scanline EXRs render option [\#512](https://github.com/pypeclub/pype/pull/512) -- Fusion: basic integration refresh [\#452](https://github.com/pypeclub/pype/pull/452) - -**Fixed bugs:** - -- Burnin script did not propagate ffmpeg output [\#640](https://github.com/pypeclub/pype/issues/640) -- Pyblish-pype spacer in terminal wasn't transparent [\#646](https://github.com/pypeclub/pype/pull/646) -- Lib subprocess without logger [\#645](https://github.com/pypeclub/pype/pull/645) -- Nuke: prevent crash if we only have single frame in sequence [\#644](https://github.com/pypeclub/pype/pull/644) -- Burnin script logs better output [\#641](https://github.com/pypeclub/pype/pull/641) -- Missing audio on farm submission. [\#639](https://github.com/pypeclub/pype/pull/639) -- review from imagesequence error [\#633](https://github.com/pypeclub/pype/pull/633) -- Hiero: wrong order of fps clip instance data collecting [\#627](https://github.com/pypeclub/pype/pull/627) -- Add source for review instances. [\#625](https://github.com/pypeclub/pype/pull/625) -- Task processing in event sync [\#623](https://github.com/pypeclub/pype/pull/623) -- sync to avalon doesn t remove renamed task [\#619](https://github.com/pypeclub/pype/pull/619) -- Intent publish setting wasn't working with default value [\#562](https://github.com/pypeclub/pype/pull/562) -- Maya: Updating a look where the shader name changed, leaves the geo without a shader [\#514](https://github.com/pypeclub/pype/pull/514) - - -### [2.12.5](https://github.com/pypeclub/pype/tree/2.12.5) - -_**release date:** 2020-10-14_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.4...2.12.5) - -**Fixed Bugs:** - -- Harmony: Disable application launch logic [\#637](https://github.com/pypeclub/pype/pull/637) - -### [2.12.4](https://github.com/pypeclub/pype/tree/2.12.4) - -_**release date:** 2020-10-08_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.3...2.12.4) - -**Fixed bugs:** - -- Sync to avalon doesn't remove renamed task [\#605](https://github.com/pypeclub/pype/issues/605) - - -**Merged pull requests:** - -- NukeStudio: small fixes [\#622](https://github.com/pypeclub/pype/pull/622) -- NukeStudio: broken order of plugins [\#620](https://github.com/pypeclub/pype/pull/620) - -### [2.12.3](https://github.com/pypeclub/pype/tree/2.12.3) - -_**release date:** 2020-10-06_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.2...2.12.3) - -**Fixed bugs:** - -- Harmony: empty scene contamination [\#583](https://github.com/pypeclub/pype/issues/583) -- Edit publishing in SP doesn't respect shot selection for publishing [\#542](https://github.com/pypeclub/pype/issues/542) -- Pathlib breaks compatibility with python2 hosts [\#281](https://github.com/pypeclub/pype/issues/281) -- Maya: fix maya scene type preset exception [\#569](https://github.com/pypeclub/pype/pull/569) -- Standalone publisher editorial plugins interfering [\#580](https://github.com/pypeclub/pype/pull/580) - -### [2.12.2](https://github.com/pypeclub/pype/tree/2.12.2) - -_**release date:** 2020-09-25_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.1...2.12.2) - -**Fixed bugs:** - -- Harmony: Saving heavy scenes will crash [\#507](https://github.com/pypeclub/pype/issues/507) -- Extract review a representation name with `\*\_burnin` [\#388](https://github.com/pypeclub/pype/issues/388) -- Hierarchy data was not considering active instances [\#551](https://github.com/pypeclub/pype/pull/551) - -### [2.12.1](https://github.com/pypeclub/pype/tree/2.12.1) - -_**release date:** 2020-09-15_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.12.0...2.12.1) - -**Fixed bugs:** - -- dependency security alert ! [\#484](https://github.com/pypeclub/pype/issues/484) -- Maya: RenderSetup is missing update [\#106](https://github.com/pypeclub/pype/issues/106) -- \ extract effects creates new instance [\#78](https://github.com/pypeclub/pype/issues/78) - - - - -## [2.12.0](https://github.com/pypeclub/pype/tree/2.12.0) ## - -_**release date:** 09 Sept 2020_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.8...2.12.0) - -**Enhancements:** - -- Pype now uses less mongo connections [\#509](https://github.com/pypeclub/pype/pull/509) -- Nuke: adding image loader [\#499](https://github.com/pypeclub/pype/pull/499) -- Completely new application launcher [\#443](https://github.com/pypeclub/pype/pull/443) -- Maya: Optional skip review on renders. [\#441](https://github.com/pypeclub/pype/pull/441) -- Ftrack: Option to push status from task to latest version [\#440](https://github.com/pypeclub/pype/pull/440) -- Maya: Properly containerize image plane loads. [\#434](https://github.com/pypeclub/pype/pull/434) -- Option to keep the review files. [\#426](https://github.com/pypeclub/pype/pull/426) -- Maya: Isolate models during preview publishing [\#425](https://github.com/pypeclub/pype/pull/425) -- Ftrack attribute group is backwards compatible [\#418](https://github.com/pypeclub/pype/pull/418) -- Maya: Publishing of tile renderings on Deadline [\#398](https://github.com/pypeclub/pype/pull/398) -- Slightly better logging gui [\#383](https://github.com/pypeclub/pype/pull/383) -- Standalonepublisher: editorial family features expansion [\#411](https://github.com/pypeclub/pype/pull/411) - -**Fixed bugs:** - -- Maya: Fix tile order for Draft Tile Assembler [\#511](https://github.com/pypeclub/pype/pull/511) -- Remove extra dash [\#501](https://github.com/pypeclub/pype/pull/501) -- Fix: strip dot from repre names in single frame renders [\#498](https://github.com/pypeclub/pype/pull/498) -- Better handling of destination during integrating [\#485](https://github.com/pypeclub/pype/pull/485) -- Fix: allow thumbnail creation for single frame renders [\#460](https://github.com/pypeclub/pype/pull/460) -- added missing argument to launch\_application in ftrack app handler [\#453](https://github.com/pypeclub/pype/pull/453) -- Burnins: Copy bit rate of input video to match quality. [\#448](https://github.com/pypeclub/pype/pull/448) -- Standalone publisher is now independent from tray [\#442](https://github.com/pypeclub/pype/pull/442) -- Bugfix/empty enumerator attributes [\#436](https://github.com/pypeclub/pype/pull/436) -- Fixed wrong order of "other" category collapssing in publisher [\#435](https://github.com/pypeclub/pype/pull/435) -- Multiple reviews where being overwritten to one. [\#424](https://github.com/pypeclub/pype/pull/424) -- Cleanup plugin fail on instances without staging dir [\#420](https://github.com/pypeclub/pype/pull/420) -- deprecated -intra parameter in ffmpeg to new `-g` [\#417](https://github.com/pypeclub/pype/pull/417) -- Delivery action can now work with entered path [\#397](https://github.com/pypeclub/pype/pull/397) - - - - - -### [2.11.8](https://github.com/pypeclub/pype/tree/2.11.8) ## - -_**release date:** 27 Aug 2020_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.7...2.11.8) - -**Fixed bugs:** - -- pyblish pype - other group is collapsed before plugins are done [\#431](https://github.com/pypeclub/pype/issues/431) -- Alpha white edges in harmony on PNGs [\#412](https://github.com/pypeclub/pype/issues/412) -- harmony image loader picks wrong representations [\#404](https://github.com/pypeclub/pype/issues/404) -- Clockify crash when response contain symbol not allowed by UTF-8 [\#81](https://github.com/pypeclub/pype/issues/81) - - - - -### [2.11.7](https://github.com/pypeclub/pype/tree/2.11.7) ## - -_**release date:** 21 Aug 2020_ - - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.6...2.11.7) - -**Fixed bugs:** - -- Clean Up Baked Movie [\#369](https://github.com/pypeclub/pype/issues/369) -- celaction last workfile wasn't picked up correctly [\#459](https://github.com/pypeclub/pype/pull/459) - - - -### [2.11.5](https://github.com/pypeclub/pype/tree/2.11.5) ## - -_**release date:** 13 Aug 2020_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.4...2.11.5) - -**Enhancements:** - -- Standalone publisher now only groups sequence if the extension is known [\#439](https://github.com/pypeclub/pype/pull/439) - -**Fixed bugs:** - -- Logs have been disable for editorial by default to speed up publishing [\#433](https://github.com/pypeclub/pype/pull/433) -- Various fixes for celaction [\#430](https://github.com/pypeclub/pype/pull/430) -- Harmony: invalid variable scope in validate scene settings [\#428](https://github.com/pypeclub/pype/pull/428) -- Harmomny: new representation name for audio was not accepted [\#427](https://github.com/pypeclub/pype/pull/427) - - - - -### [2.11.3](https://github.com/pypeclub/pype/tree/2.11.3) ## - -_**release date:** 4 Aug 2020_ - -[Full Changelog](https://github.com/pypeclub/pype/compare/2.11.2...2.11.3) - -**Fixed bugs:** - -- Harmony: publishing performance issues [\#408](https://github.com/pypeclub/pype/pull/408) - - - - -## 2.11.0 ## - -_**release date:** 27 July 2020_ - -**new:** -- _(blender)_ namespace support [\#341](https://github.com/pypeclub/pype/pull/341) -- _(blender)_ start end frames [\#330](https://github.com/pypeclub/pype/pull/330) -- _(blender)_ camera asset [\#322](https://github.com/pypeclub/pype/pull/322) -- _(pype)_ toggle instances per family in pyblish GUI [\#320](https://github.com/pypeclub/pype/pull/320) -- _(pype)_ current release version is now shown in the tray menu [#379](https://github.com/pypeclub/pype/pull/379) - - -**improved:** -- _(resolve)_ tagging for publish [\#239](https://github.com/pypeclub/pype/issues/239) -- _(pype)_ Support publishing a subset of shots with standalone editorial [\#336](https://github.com/pypeclub/pype/pull/336) -- _(harmony)_ Basic support for palettes [\#324](https://github.com/pypeclub/pype/pull/324) -- _(photoshop)_ Flag outdated containers on startup and publish. [\#309](https://github.com/pypeclub/pype/pull/309) -- _(harmony)_ Flag Outdated containers [\#302](https://github.com/pypeclub/pype/pull/302) -- _(photoshop)_ Publish review [\#298](https://github.com/pypeclub/pype/pull/298) -- _(pype)_ Optional Last workfile launch [\#365](https://github.com/pypeclub/pype/pull/365) - - -**fixed:** -- _(premiere)_ workflow fixes [\#346](https://github.com/pypeclub/pype/pull/346) -- _(pype)_ pype-setup does not work with space in path [\#327](https://github.com/pypeclub/pype/issues/327) -- _(ftrack)_ Ftrack delete action cause circular error [\#206](https://github.com/pypeclub/pype/issues/206) -- _(nuke)_ Priority was forced to 50 [\#345](https://github.com/pypeclub/pype/pull/345) -- _(nuke)_ Fix ValidateNukeWriteKnobs [\#340](https://github.com/pypeclub/pype/pull/340) -- _(maya)_ If camera attributes are connected, we can ignore them. [\#339](https://github.com/pypeclub/pype/pull/339) -- _(pype)_ stop appending of tools environment to existing env [\#337](https://github.com/pypeclub/pype/pull/337) -- _(ftrack)_ Ftrack timeout needs to look at AVALON\_TIMEOUT [\#325](https://github.com/pypeclub/pype/pull/325) -- _(harmony)_ Only zip files are supported. [\#310](https://github.com/pypeclub/pype/pull/310) -- _(pype)_ hotfix/Fix event server mongo uri [\#305](https://github.com/pypeclub/pype/pull/305) -- _(photoshop)_ Subset was not named or validated correctly. [\#304](https://github.com/pypeclub/pype/pull/304) - - - - - -## 2.10.0 ## - -_**release date:** 17 June 2020_ - -**new:** -- _(harmony)_ **Toon Boom Harmony** has been greatly extended to support rigging, scene build, animation and rendering workflows. [#270](https://github.com/pypeclub/pype/issues/270) [#271](https://github.com/pypeclub/pype/issues/271) [#190](https://github.com/pypeclub/pype/issues/190) [#191](https://github.com/pypeclub/pype/issues/191) [#172](https://github.com/pypeclub/pype/issues/172) [#168](https://github.com/pypeclub/pype/issues/168) -- _(pype)_ Added support for rudimentary **edl publishing** into individual shots. [#265](https://github.com/pypeclub/pype/issues/265) -- _(celaction)_ Simple **Celaction** integration has been added with support for workfiles and rendering. [#255](https://github.com/pypeclub/pype/issues/255) -- _(maya)_ Support for multiple job types when submitting to the farm. We can now render Maya or Standalone render jobs for Vray and Arnold (limited support for arnold) [#204](https://github.com/pypeclub/pype/issues/204) -- _(photoshop)_ Added initial support for Photoshop [#232](https://github.com/pypeclub/pype/issues/232) - -**improved:** -- _(blender)_ Updated support for rigs and added support Layout family [#233](https://github.com/pypeclub/pype/issues/233) [#226](https://github.com/pypeclub/pype/issues/226) -- _(premiere)_ It is now possible to choose different storage root for workfiles of different task types. [#255](https://github.com/pypeclub/pype/issues/255) -- _(maya)_ Support for unmerged AOVs in Redshift multipart EXRs [#197](https://github.com/pypeclub/pype/issues/197) -- _(pype)_ Pype repository has been refactored in preparation for 3.0 release [#169](https://github.com/pypeclub/pype/issues/169) -- _(deadline)_ All file dependencies are now passed to deadline from maya to prevent premature start of rendering if caches or textures haven't been coppied over yet. [#195](https://github.com/pypeclub/pype/issues/195) -- _(nuke)_ Script validation can now be made optional. [#194](https://github.com/pypeclub/pype/issues/194) -- _(pype)_ Publishing can now be stopped at any time. [#194](https://github.com/pypeclub/pype/issues/194) - -**fix:** -- _(pype)_ Pyblish-lite has been integrated into pype repository, plus various publishing GUI fixes. [#274](https://github.com/pypeclub/pype/issues/274) [#275](https://github.com/pypeclub/pype/issues/275) [#268](https://github.com/pypeclub/pype/issues/268) [#227](https://github.com/pypeclub/pype/issues/227) [#238](https://github.com/pypeclub/pype/issues/238) -- _(maya)_ Alembic extractor was getting wrong frame range type in certain scenarios [#254](https://github.com/pypeclub/pype/issues/254) -- _(maya)_ Attaching a render to subset in maya was not passing validation in certain scenarios [#256](https://github.com/pypeclub/pype/issues/256) -- _(ftrack)_ Various small fixes to ftrack sync [#263](https://github.com/pypeclub/pype/issues/263) [#259](https://github.com/pypeclub/pype/issues/259) -- _(maya)_ Look extraction is now able to skp invalid connections in shaders [#207](https://github.com/pypeclub/pype/issues/207) - - - - - -## 2.9.0 ## - -_**release date:** 25 May 2020_ - -**new:** -- _(pype)_ Support for **Multiroot projects**. You can now store project data on multiple physical or virtual storages and target individual publishes to these locations. For instance render can be stored on a faster storage than the rest of the project. [#145](https://github.com/pypeclub/pype/issues/145), [#38](https://github.com/pypeclub/pype/issues/38) -- _(harmony)_ Basic implementation of **Toon Boom Harmony** has been added. [#142](https://github.com/pypeclub/pype/issues/142) -- _(pype)_ OSX support is in public beta now. There are issues to be expected, but the main implementation should be functional. [#141](https://github.com/pypeclub/pype/issues/141) - - -**improved:** - -- _(pype)_ **Review extractor** has been completely rebuilt. It now supports granular filtering so you can create **multiple outputs** for different tasks, families or hosts. [#103](https://github.com/pypeclub/pype/issues/103), [#166](https://github.com/pypeclub/pype/issues/166), [#165](https://github.com/pypeclub/pype/issues/165) -- _(pype)_ **Burnin** generation had been extended to **support same multi-output filtering** as review extractor [#103](https://github.com/pypeclub/pype/issues/103) -- _(pype)_ Publishing file templates can now be specified in config for each individual family [#114](https://github.com/pypeclub/pype/issues/114) -- _(pype)_ Studio specific plugins can now be appended to pype standard publishing plugins. [#112](https://github.com/pypeclub/pype/issues/112) -- _(nukestudio)_ Reviewable clips no longer need to be previously cut, exported and re-imported to timeline. **Pype can now dynamically cut reviewable quicktimes** from continuous offline footage during publishing. [#23](https://github.com/pypeclub/pype/issues/23) -- _(deadline)_ Deadline can now correctly differentiate between staging and production pype. [#154](https://github.com/pypeclub/pype/issues/154) -- _(deadline)_ `PYPE_PYTHON_EXE` env variable can now be used to direct publishing to explicit python installation. [#120](https://github.com/pypeclub/pype/issues/120) -- _(nuke)_ Nuke now check for new version of loaded data on file open. [#140](https://github.com/pypeclub/pype/issues/140) -- _(nuke)_ frame range and limit checkboxes are now exposed on write node. [#119](https://github.com/pypeclub/pype/issues/119) - - - -**fix:** - -- _(nukestudio)_ Project Location was using backslashes which was breaking nukestudio native exporting in certains configurations [#82](https://github.com/pypeclub/pype/issues/82) -- _(nukestudio)_ Duplicity in hierarchy tags was prone to throwing publishing error [#130](https://github.com/pypeclub/pype/issues/130), [#144](https://github.com/pypeclub/pype/issues/144) -- _(ftrack)_ multiple stability improvements [#157](https://github.com/pypeclub/pype/issues/157), [#159](https://github.com/pypeclub/pype/issues/159), [#128](https://github.com/pypeclub/pype/issues/128), [#118](https://github.com/pypeclub/pype/issues/118), [#127](https://github.com/pypeclub/pype/issues/127) -- _(deadline)_ multipart EXRs were stopping review publishing on the farm. They are still not supported for automatic review generation, but the publish will go through correctly without the quicktime. [#155](https://github.com/pypeclub/pype/issues/155) -- _(deadline)_ If deadline is non-responsive it will no longer freeze host when publishing [#149](https://github.com/pypeclub/pype/issues/149) -- _(deadline)_ Sometimes deadline was trying to launch render before all the source data was coppied over. [#137](https://github.com/pypeclub/pype/issues/137) _(harmony)_ Basic implementation of **Toon Boom Harmony** has been added. [#142](https://github.com/pypeclub/pype/issues/142) -- _(nuke)_ Filepath knob wasn't updated properly. [#131](https://github.com/pypeclub/pype/issues/131) -- _(maya)_ When extracting animation, the "Write Color Set" options on the instance were not respected. [#108](https://github.com/pypeclub/pype/issues/108) -- _(maya)_ Attribute overrides for AOV only worked for the legacy render layers. Now it works for new render setup as well [#132](https://github.com/pypeclub/pype/issues/132) -- _(maya)_ Stability and usability improvements in yeti workflow [#104](https://github.com/pypeclub/pype/issues/104) - - - - - -## 2.8.0 ## - -_**release date:** 20 April 2020_ - -**new:** - -- _(pype)_ Option to generate slates from json templates. [PYPE-628] [#26](https://github.com/pypeclub/pype/issues/26) -- _(pype)_ It is now possible to automate loading of published subsets into any scene. Documentation will follow :). [PYPE-611] [#24](https://github.com/pypeclub/pype/issues/24) - -**fix:** - -- _(maya)_ Some Redshift render tokens could break publishing. [PYPE-778] [#33](https://github.com/pypeclub/pype/issues/33) -- _(maya)_ Publish was not preserving maya file extension. [#39](https://github.com/pypeclub/pype/issues/39) -- _(maya)_ Rig output validator was failing on nodes without shapes. [#40](https://github.com/pypeclub/pype/issues/40) -- _(maya)_ Yeti caches can now be properly versioned up in the scene inventory. [#40](https://github.com/pypeclub/pype/issues/40) -- _(nuke)_ Build first workfiles was not accepting jpeg sequences. [#34](https://github.com/pypeclub/pype/issues/34) -- _(deadline)_ Trying to generate ffmpeg review from multipart EXRs no longer crashes publishing. [PYPE-781] -- _(deadline)_ Render publishing is more stable in multiplatform environments. [PYPE-775] - - - - - -## 2.7.0 ## - -_**release date:** 30 March 2020_ - -**new:** - -- _(maya)_ Artist can now choose to load multiple references of the same subset at once [PYPE-646, PYPS-81] -- _(nuke)_ Option to use named OCIO colorspaces for review colour baking. [PYPS-82] -- _(pype)_ Pype can now work with `master` versions for publishing and loading. These are non-versioned publishes that are overwritten with the latest version during publish. These are now supported in all the GUIs, but their publishing is deactivated by default. [PYPE-653] -- _(blender)_ Added support for basic blender workflow. We currently support `rig`, `model` and `animation` families. [PYPE-768] -- _(pype)_ Source timecode can now be used in burn-ins. [PYPE-777] -- _(pype)_ Review outputs profiles can now specify delivery resolution different than project setting [PYPE-759] -- _(nuke)_ Bookmark to current context is now added automatically to all nuke browser windows. [PYPE-712] - -**change:** - -- _(maya)_ It is now possible to publish camera without. baking. Keep in mind that unbaked cameras can't be guaranteed to work in other hosts. [PYPE-595] -- _(maya)_ All the renders from maya are now grouped in the loader by their Layer name. [PYPE-482] -- _(nuke/hiero)_ Any publishes from nuke and hiero can now be versioned independently of the workfile. [PYPE-728] - - -**fix:** - -- _(nuke)_ Mixed slashes caused issues in ocio config path. -- _(pype)_ Intent field in pyblish GUI was passing label instead of value to ftrack. [PYPE-733] -- _(nuke)_ Publishing of pre-renders was inconsistent. [PYPE-766] -- _(maya)_ Handles and frame ranges were inconsistent in various places during publishing. -- _(nuke)_ Nuke was crashing if it ran into certain missing knobs. For example DPX output missing `autocrop` [PYPE-774] -- _(deadline)_ Project overrides were not working properly with farm render publishing. -- _(hiero)_ Problems with single frame plates publishing. -- _(maya)_ Redshift RenderPass token were breaking render publishing. [PYPE-778] -- _(nuke)_ Build first workfile was not accepting jpeg sequences. -- _(maya)_ Multipart (Multilayer) EXRs were breaking review publishing due to FFMPEG incompatiblity [PYPE-781] - - - - -## 2.6.0 ## - -_**release date:** 9 March 2020_ - -**change:** -- _(maya)_ render publishing has been simplified and made more robust. Render setup layers are now automatically added to publishing subsets and `render globals` family has been replaced with simple `render` [PYPE-570] -- _(avalon)_ change context and workfiles apps, have been merged into one, that allows both actions to be performed at the same time. [PYPE-747] -- _(pype)_ thumbnails are now automatically propagate to asset from the last published subset in the loader -- _(ftrack)_ publishing comment and intent are now being published to ftrack note as well as describtion. [PYPE-727] -- _(pype)_ when overriding existing version new old representations are now overriden, instead of the new ones just being appended. (to allow this behaviour, the version validator need to be disabled. [PYPE-690]) -- _(pype)_ burnin preset has been significantly simplified. It now doesn't require passing function to each field, but only need the actual text template. to use this, all the current burnin PRESETS MUST BE UPDATED for all the projects. -- _(ftrack)_ credentials are now stored on a per server basis, so it's possible to switch between ftrack servers without having to log in and out. [PYPE-723] - - -**new:** -- _(pype)_ production and development deployments now have different colour of the tray icon. Orange for Dev and Green for production [PYPE-718] -- _(maya)_ renders can now be attached to a publishable subset rather than creating their own subset. For example it is possible to create a reviewable `look` or `model` render and have it correctly attached as a representation of the subsets [PYPE-451] -- _(maya)_ after saving current scene into a new context (as a new shot for instance), all the scene publishing subsets data gets re-generated automatically to match the new context [PYPE-532] -- _(pype)_ we now support project specific publish, load and create plugins [PYPE-740] -- _(ftrack)_ new action that allow archiving/deleting old published versions. User can keep how many of the latest version to keep when the action is ran. [PYPE-748, PYPE-715] -- _(ftrack)_ it is now possible to monitor and restart ftrack event server using ftrack action. [PYPE-658] -- _(pype)_ validator that prevent accidental overwrites of previously published versions. [PYPE-680] -- _(avalon)_ avalon core updated to version 5.6.0 -- _(maya)_ added validator to make sure that relative paths are used when publishing arnold standins. -- _(nukestudio)_ it is now possible to extract and publish audio family from clip in nuke studio [PYPE-682] - -**fix**: -- _(maya)_ maya set framerange button was ignoring handles [PYPE-719] -- _(ftrack)_ sync to avalon was sometime crashing when ran on empty project -- _(nukestudio)_ publishing same shots after they've been previously archived/deleted would result in a crash. [PYPE-737] -- _(nuke)_ slate workflow was breaking in certain scenarios. [PYPE-730] -- _(pype)_ rendering publish workflow has been significantly improved to prevent error resulting from implicit render collection. [PYPE-665, PYPE-746] -- _(pype)_ launching application on a non-synced project resulted in obscure [PYPE-528] -- _(pype)_ missing keys in burnins no longer result in an error. [PYPE-706] -- _(ftrack)_ create folder structure action was sometimes failing for project managers due to wrong permissions. -- _(Nukestudio)_ using `source` in the start frame tag could result in wrong frame range calculation -- _(ftrack)_ sync to avalon action and event have been improved by catching more edge cases and provessing them properly. - - - - -## 2.5.0 ## - -_**release date:** 11 Feb 2020_ - -**change:** -- _(pype)_ added many logs for easier debugging -- _(pype)_ review presets can now be separated between 2d and 3d renders [PYPE-693] -- _(pype)_ anatomy module has been greatly improved to allow for more dynamic pulblishing and faster debugging [PYPE-685] -- _(pype)_ avalon schemas have been moved from `pype-config` to `pype` repository, for simplification. [PYPE-670] -- _(ftrack)_ updated to latest ftrack API -- _(ftrack)_ publishing comments now appear in ftrack also as a note on version with customisable category [PYPE-645] -- _(ftrack)_ delete asset/subset action had been improved. It is now able to remove multiple entities and descendants of the selected entities [PYPE-361, PYPS-72] -- _(workfiles)_ added date field to workfiles app [PYPE-603] -- _(maya)_ old deprecated loader have been removed in favour of a single unified reference loader (old scenes will upgrade automatically to the new loader upon opening) [PYPE-633, PYPE-697] -- _(avalon)_ core updated to 5.5.15 [PYPE-671] -- _(nuke)_ library loader is now available in nuke [PYPE-698] - - -**new:** -- _(pype)_ added pype render wrapper to allow rendering on mixed platform farms. [PYPE-634] -- _(pype)_ added `pype launch` command. It let's admin run applications with dynamically built environment based on the given context. [PYPE-634] -- _(pype)_ added support for extracting review sequences with burnins [PYPE-657] -- _(publish)_ users can now set intent next to a comment when publishing. This will then be reflected on an attribute in ftrack. [PYPE-632] -- _(burnin)_ timecode can now be added to burnin -- _(burnin)_ datetime keys can now be added to burnin and anatomy [PYPE-651] -- _(burnin)_ anatomy templates can now be used in burnins. [PYPE=626] -- _(nuke)_ new validator for render resolution -- _(nuke)_ support for attach slate to nuke renders [PYPE-630] -- _(nuke)_ png sequences were added to loaders -- _(maya)_ added maya 2020 compatibility [PYPE-677] -- _(maya)_ ability to publish and load .ASS standin sequences [PYPS-54] -- _(pype)_ thumbnails can now be published and are visible in the loader. `AVALON_THUMBNAIL_ROOT` environment variable needs to be set for this to work [PYPE-573, PYPE-132] -- _(blender)_ base implementation of blender was added with publishing and loading of .blend files [PYPE-612] -- _(ftrack)_ new action for preparing deliveries [PYPE-639] - - -**fix**: -- _(burnin)_ more robust way of finding ffmpeg for burnins. -- _(pype)_ improved UNC paths remapping when sending to farm. -- _(pype)_ float frames sometimes made their way to representation context in database, breaking loaders [PYPE-668] -- _(pype)_ `pype install --force` was failing sometimes [PYPE-600] -- _(pype)_ padding in published files got calculated wrongly sometimes. It is now instead being always read from project anatomy. [PYPE-667] -- _(publish)_ comment publishing was failing in certain situations -- _(ftrack)_ multiple edge case scenario fixes in auto sync and sync-to-avalon action -- _(ftrack)_ sync to avalon now works on empty projects -- _(ftrack)_ thumbnail update event was failing when deleting entities [PYPE-561] -- _(nuke)_ loader applies proper colorspaces from Presets -- _(nuke)_ publishing handles didn't always work correctly [PYPE-686] -- _(maya)_ assembly publishing and loading wasn't working correctly - - - - - - -## 2.4.0 ## - -_**release date:** 9 Dec 2019_ - -**change:** -- _(ftrack)_ version to status ftrack event can now be configured from Presets - - based on preset `presets/ftracc/ftrack_config.json["status_version_to_task"]` -- _(ftrack)_ sync to avalon event has been completely re-written. It now supports most of the project management situations on ftrack including moving, renaming and deleting entities, updating attributes and working with tasks. -- _(ftrack)_ sync to avalon action has been also re-writen. It is now much faster (up to 100 times depending on a project structure), has much better logging and reporting on encountered problems, and is able to handle much more complex situations. -- _(ftrack)_ sync to avalon trigger by checking `auto-sync` toggle on ftrack [PYPE-504] -- _(pype)_ various new features in the REST api -- _(pype)_ new visual identity used across pype -- _(pype)_ started moving all requirements to pip installation rather than vendorising them in pype repository. Due to a few yet unreleased packages, this means that pype can temporarily be only installed in the offline mode. - -**new:** -- _(nuke)_ support for publishing gizmos and loading them as viewer processes -- _(nuke)_ support for publishing nuke nodes from backdrops and loading them back -- _(pype)_ burnins can now work with start and end frames as keys - - use keys `{frame_start}`, `{frame_end}` and `{current_frame}` in burnin preset to use them. [PYPS-44,PYPS-73, PYPE-602] -- _(pype)_ option to filter logs by user and level in loggin GUI -- _(pype)_ image family added to standalone publisher [PYPE-574] -- _(pype)_ matchmove family added to standalone publisher [PYPE-574] -- _(nuke)_ validator for comparing arbitrary knobs with values from presets -- _(maya)_ option to force maya to copy textures in the new look publish rather than hardlinking them -- _(pype)_ comments from pyblish GUI are now being added to ftrack version -- _(maya)_ validator for checking outdated containers in the scene -- _(maya)_ option to publish and load arnold standin sequence [PYPE-579, PYPS-54] - -**fix**: -- _(pype)_ burnins were not respecting codec of the input video -- _(nuke)_ lot's of various nuke and nuke studio fixes across the board [PYPS-45] -- _(pype)_ workfiles app is not launching with the start of the app by default [PYPE-569] -- _(ftrack)_ ftrack integration during publishing was failing under certain situations [PYPS-66] -- _(pype)_ minor fixes in REST api -- _(ftrack)_ status change event was crashing when the target status was missing [PYPS-68] -- _(ftrack)_ actions will try to reconnect if they fail for some reason -- _(maya)_ problems with fps mapping when using float FPS values -- _(deadline)_ overall improvements to deadline publishing -- _(setup)_ environment variables are now remapped on the fly based on the platform pype is running on. This fixes many issues in mixed platform environments. - - - - -## 2.3.6 # - -_**release date:** 27 Nov 2019_ - -**hotfix**: -- _(ftrack)_ was hiding important debug logo -- _(nuke)_ crashes during workfile publishing -- _(ftrack)_ event server crashes because of signal problems -- _(muster)_ problems with muster render submissions -- _(ftrack)_ thumbnail update event syntax errors - - - - -## 2.3.0 ## - -_release date: 6 Oct 2019_ - -**new**: -- _(maya)_ support for yeti rigs and yeti caches -- _(maya)_ validator for comparing arbitrary attributes against ftrack -- _(pype)_ burnins can now show current date and time -- _(muster)_ pools can now be set in render globals in maya -- _(pype)_ Rest API has been implemented in beta stage -- _(nuke)_ LUT loader has been added -- _(pype)_ rudimentary user module has been added as preparation for user management -- _(pype)_ a simple logging GUI has been added to pype tray -- _(nuke)_ nuke can now bake input process into mov -- _(maya)_ imported models now have selection handle displayed by defaulting -- _(avalon)_ it's is now possible to load multiple assets at once using loader -- _(maya)_ added ability to automatically connect yeti rig to a mesh upon loading - -**changed**: -- _(ftrack)_ event server now runs two parallel processes and is able to keep queue of events to process. -- _(nuke)_ task name is now added to all rendered subsets -- _(pype)_ adding more families to standalone publisher -- _(pype)_ standalone publisher now uses pyblish-lite -- _(pype)_ standalone publisher can now create review quicktimes -- _(ftrack)_ queries to ftrack were sped up -- _(ftrack)_ multiple ftrack action have been deprecated -- _(avalon)_ avalon upstream has been updated to 5.5.0 -- _(nukestudio)_ published transforms can now be animated -- - -**fix**: -- _(maya)_ fps popup button didn't work in some cases -- _(maya)_ geometry instances and references in maya were losing shader assignments -- _(muster)_ muster rendering templates were not working correctly -- _(maya)_ arnold tx texture conversion wasn't respecting colorspace set by the artist -- _(pype)_ problems with avalon db sync -- _(maya)_ ftrack was rounding FPS making it inconsistent -- _(pype)_ wrong icon names in Creator -- _(maya)_ scene inventory wasn't showing anything if representation was removed from database after it's been loaded to the scene -- _(nukestudio)_ multiple bugs squashed -- _(loader)_ loader was taking long time to show all the loading action when first launcher in maya - -## 2.2.0 ## -_**release date:** 8 Sept 2019_ - -**new**: -- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts -- _(nuke)_ option to choose deadline chunk size on write nodes -- _(nukestudio)_ added option to publish soft effects (subTrackItems) from NukeStudio as subsets including LUT files. these can then be loaded in nuke or NukeStudio -- _(nuke)_ option to build nuke script from previously published latest versions of plate and render subsets. -- _(nuke)_ nuke writes now have deadline tab. -- _(ftrack)_ Prepare Project action can now be used for creating the base folder structure on disk and in ftrack, setting up all the initial project attributes and it automatically prepares `pype_project_config` folder for the given project. -- _(clockify)_ Added support for time tracking in clockify. This currently in addition to ftrack time logs, but does not completely replace them. -- _(pype)_ any attributes in Creator and Loader plugins can now be customised using pype preset system - -**changed**: -- nukestudio now uses workio API for workfiles -- _(maya)_ "FIX FPS" prompt in maya now appears in the middle of the screen -- _(muster)_ can now be configured with custom templates -- _(pype)_ global publishing plugins can now be configured using presets as well as host specific ones - - -**fix**: -- wrong version retrieval from path in certain scenarios -- nuke reset resolution wasn't working in certain scenarios - -## 2.1.0 ## -_release date: 6 Aug 2019_ - -A large cleanup release. Most of the change are under the hood. - -**new**: -- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts -- _(pype)_ Added configurable option to add burnins to any generated quicktimes -- _(ftrack)_ Action that identifies what machines pype is running on. -- _(system)_ unify subprocess calls -- _(maya)_ add audio to review quicktimes -- _(nuke)_ add crop before write node to prevent overscan problems in ffmpeg -- **Nuke Studio** publishing and workfiles support -- **Muster** render manager support -- _(nuke)_ Framerange, FPS and Resolution are set automatically at startup -- _(maya)_ Ability to load published sequences as image planes -- _(system)_ Ftrack event that sets asset folder permissions based on task assignees in ftrack. -- _(maya)_ Pyblish plugin that allow validation of maya attributes -- _(system)_ added better startup logging to tray debug, including basic connection information -- _(avalon)_ option to group published subsets to groups in the loader -- _(avalon)_ loader family filters are working now - -**changed**: -- change multiple key attributes to unify their behaviour across the pipeline - - `frameRate` to `fps` - - `startFrame` to `frameStart` - - `endFrame` to `frameEnd` - - `fstart` to `frameStart` - - `fend` to `frameEnd` - - `handle_start` to `handleStart` - - `handle_end` to `handleEnd` - - `resolution_width` to `resolutionWidth` - - `resolution_height` to `resolutionHeight` - - `pixel_aspect` to `pixelAspect` - -- _(nuke)_ write nodes are now created inside group with only some attributes editable by the artist -- rendered frames are now deleted from temporary location after their publishing is finished. -- _(ftrack)_ RV action can now be launched from any entity -- after publishing only refresh button is now available in pyblish UI -- added context instance pyblish-lite so that artist knows if context plugin fails -- _(avalon)_ allow opening selected files using enter key -- _(avalon)_ core updated to v5.2.9 with our forked changes on top - -**fix**: -- faster hierarchy retrieval from db -- _(nuke)_ A lot of stability enhancements -- _(nuke studio)_ A lot of stability enhancements -- _(nuke)_ now only renders a single write node on farm -- _(ftrack)_ pype would crash when launcher project level task -- work directory was sometimes not being created correctly -- major pype.lib cleanup. Removing of unused functions, merging those that were doing the same and general house cleaning. -- _(avalon)_ subsets in maya 2019 weren't behaving correctly in the outliner diff --git a/website/docs/upgrade_notes.md b/website/docs/upgrade_notes.md deleted file mode 100644 index 8231cf997d..0000000000 --- a/website/docs/upgrade_notes.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -id: update_notes -title: Update Notes -sidebar_label: Update Notes ---- - - - -## **Updating to 2.13.0** ## - -### MongoDB - -**Must** - -Due to changes in how tasks are stored in the database (we added task types and possibility of more arbitrary data.), we must take a few precautions when updating. -1. Make sure that ftrack event server with sync to avalon is NOT running during the update. -2. Any project that is to be worked on with 2.13 must be synced from ftrack to avalon with the updated sync to avalon action, or using and updated event server sync to avalon event. - -If 2.12 event servers runs when trying to update the project sync with 2.13, it will override any changes. - -### Nuke Studio / hiero - -Make sure to re-generate pype tags and replace any `task` tags on your shots with the new ones. This will allow you to make multiple tasks of the same type, but with different task name at the same time. - -### Nuke - -Due to a minor update to nuke write node, artists will be prompted to update their write nodes before being able to publish any old shots. There is a "repair" action for this in the publisher, so it doesn't have to be done manually. - - - - -## **Updating to 2.12.0** ## - -### Apps and tools - -**Must** - -run Create/Update Custom attributes action (to update custom attributes group) -check if studio has set custom intent values and move values to ~/config/presets/global/intent.json - -**Optional** - -Set true/false on application and tools by studio usage (eliminate app list in Ftrack and time for registering Ftrack ations) - - - - -## **Updating to 2.11.0** ## - -### Maya in deadline - -We added or own maya deadline plugin to make render management easier. It operates the same as standard mayaBatch in deadline, but allow us to separate Pype sumitted jobs from standard submitter. You'll need to follow this guide to update this [install pype deadline](https://pype.club/docs/admin_hosts#pype-dealine-supplement-code) - - - - -## **Updating to 2.9.0** ## - -### Review and Burnin PRESETS - -This release introduces a major update to working with review and burnin presets. They can now be much more granular and can target extremely specific usecases. The change is backwards compatible with previous format of review and burnin presets, however we highly recommend updating all the presets to the new format. Documentation on what this looks like can be found on pype main [documentation page](https://pype.club/docs/admin_presets_plugins#publishjson). - -### Multiroot and storages - -With the support of multiroot projects, we removed the old `storage.json` from configuration and replaced it with simpler `config/anatomy/roots.json`. This is a required change, but only needs to be done once per studio during the update to 2.9.0. [Read More](https://pype.club/docs/next/admin_config#roots) - - - - -## **Updating to 2.7.0** ## - -### Master Versions -To activate `master` version workflow you need to activate `integrateMasterVersion` plugin in the `config/presets/plugins/global/publish.json` - -``` -"IntegrateMasterVersion": {"enabled": true}, -``` - -### Ftrack - -Make sure that `intent` attributes in ftrack is set correctly. It should follow this setup unless you have your custom values -``` -{ - "label": "Intent", - "key": "intent", - "type": "enumerator", - "entity_type": "assetversion", - "group": "avalon", - "config": { - "multiselect": false, - "data": [ - {"test": "Test"}, - {"wip": "WIP"}, - {"final": "Final"} - ] - } -``` - - - - -## **Updating to 2.6.0** ## - -### Dev vs Prod - -If you want to differentiate between dev and prod deployments of pype, you need to add `config.ini` file to `pype-setup/pypeapp` folder with content. - -``` -[Default] -dev=true -``` - -### Ftrack - -You will have to log in to ftrack in pype after the update. You should be automatically prompted with the ftrack login window when you launch 2.6 release for the first time. - -Event server has to be restarted after the update to enable the ability to control it via action. - -### Presets - -There is a major change in the way how burnin presets are being stored. We simplified the preset format, however that means the currently running production configs need to be tweaked to match the new format. - -:::note Example of converting burnin preset from 2.5 to 2.6 - -2.5 burnin preset - -``` -"burnins":{ - "TOP_LEFT": { - "function": "text", - "text": "{dd}/{mm}/{yyyy}" - }, - "TOP_CENTERED": { - "function": "text", - "text": "" - }, - "TOP_RIGHT": { - "function": "text", - "text": "v{version:0>3}" - }, - "BOTTOM_LEFT": { - "function": "text", - "text": "{frame_start}-{current_frame}-{frame_end}" - }, - "BOTTOM_CENTERED": { - "function": "text", - "text": "{asset}" - }, - "BOTTOM_RIGHT": { - "function": "frame_numbers", - "text": "{username}" - } -``` - -2.6 burnin preset -``` -"burnins":{ - "TOP_LEFT": "{dd}/{mm}/{yyyy}", - "TOP_CENTER": "", - "TOP_RIGHT": "v{version:0>3}" - "BOTTOM_LEFT": "{frame_start}-{current_frame}-{frame_end}", - "BOTTOM_CENTERED": "{asset}", - "BOTTOM_RIGHT": "{username}" -} -``` diff --git a/website/sidebars.js b/website/sidebars.js index 9d60a5811c..c4d07e728f 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -109,11 +109,7 @@ module.exports = { "admin_hosts_tvpaint" ], }, - { - type: "category", - label: "Releases", - items: ["changelog", "update_notes"], - }, + "admin_releases", { type: "category", collapsed: false, From 7466063001d7efce8ef63302e22ac50607be20bf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:41:02 +0200 Subject: [PATCH 089/101] Fix typo Co-authored-by: Milan Kolar --- website/docs/system_introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/system_introduction.md b/website/docs/system_introduction.md index b8a2cea487..05627b5359 100644 --- a/website/docs/system_introduction.md +++ b/website/docs/system_introduction.md @@ -17,7 +17,7 @@ various usage scenarios. You can find detailed breakdown of technical requirements [here](dev_requirements), but in general OpenPype should be able to operate in most studios fairly quickly. The main obstacles are usually related to workflows and habits, that -might now be fully compatible with what OpenPype is expecting or enforcing. It is recommended to go through artists [key concepts](artist_concepts) to get idea about basics. +might not be fully compatible with what OpenPype is expecting or enforcing. It is recommended to go through artists [key concepts](artist_concepts) to get idea about basics. Keep in mind that if you run into any workflows that are not supported, it's usually just because we haven't hit that particular case and it can most likely be added upon request. From bf5584d77f9766dc5da23890c76dba1841c6bfdb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 23 Aug 2022 11:46:47 +0200 Subject: [PATCH 090/101] Change avalon to openpype Co-authored-by: Roy Nieterau --- website/docs/module_ftrack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/module_ftrack.md b/website/docs/module_ftrack.md index ad9cf75e8f..6d5529b512 100644 --- a/website/docs/module_ftrack.md +++ b/website/docs/module_ftrack.md @@ -72,7 +72,7 @@ We do not recommend setting your Ftrack user and api key environments in a persi ### Where to run event server -We recommend you to run event server on stable server machine with ability to connect to Avalon database and Ftrack web server. Best practice we recommend is to run event server as service. It can be Windows or Linux. +We recommend you to run event server on stable server machine with ability to connect to OpenPype database and Ftrack web server. Best practice we recommend is to run event server as service. It can be Windows or Linux. :::important Event server should **not** run more than once! It may cause major issues. From 4943b7889eecb207304683b01dca778d376dc9ee Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 23 Aug 2022 11:47:07 +0200 Subject: [PATCH 091/101] grammar fix Co-authored-by: Roy Nieterau --- website/docs/artist_concepts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_concepts.md b/website/docs/artist_concepts.md index f67ab89b9c..7582540811 100644 --- a/website/docs/artist_concepts.md +++ b/website/docs/artist_concepts.md @@ -10,7 +10,7 @@ sidebar_label: Key Concepts In our pipeline all the main entities the project is made from are internally considered *'Assets'*. Episode, sequence, shot, character, prop, etc. All of these behave identically in the pipeline. Asset names need to be absolutely unique within the project because they are their key identifier. -OpenPype has limitation regarging duplicated names. Name of assets must be unique across whole project. +OpenPype has a limitation regarding duplicated names. Name of assets must be unique across whole project. ### Subset From 6a42f07d8e5977193236f9b3665a5a655188ae1c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 15:05:55 +0200 Subject: [PATCH 092/101] fix missing argument --- openpype/hosts/tvpaint/tvpaint_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/tvpaint_module.py b/openpype/hosts/tvpaint/tvpaint_module.py index c29602babc..a004359231 100644 --- a/openpype/hosts/tvpaint/tvpaint_module.py +++ b/openpype/hosts/tvpaint/tvpaint_module.py @@ -20,7 +20,7 @@ class TVPaintModule(OpenPypeModule, IHostModule): def initialize(self, module_settings): self.enabled = True - def add_implementation_envs(env, _app): + def add_implementation_envs(self, env, _app): """Modify environments to contain all required for implementation.""" defaults = { From 9c2c1118ac40d6f8ec3b691a947c2a985439da83 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 15:07:47 +0200 Subject: [PATCH 093/101] added notes into client directory --- openpype/client/notes.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 openpype/client/notes.md diff --git a/openpype/client/notes.md b/openpype/client/notes.md new file mode 100644 index 0000000000..a261b86eca --- /dev/null +++ b/openpype/client/notes.md @@ -0,0 +1,39 @@ +# Client functionality +## Reason +Preparation for OpenPype v4 server. Goal is to remove direct mongo calls in code to prepare a little bit for different source of data for code before. To start think about database calls less as mongo calls but more universally. To do so was implemented simple wrapper around database calls to not use pymongo specific code. + +Current goal is not to make universal database model which can be easily replaced with any different source of data but to make it close as possible. Current implementation of OpenPype is too tighly connected to pymongo and it's abilities so we're trying to get closer with long term changes that can be used even in current state. + +## Queries +Query functions don't use full potential of mongo queries like very specific queries based on subdictionaries or unknown structures. We try to avoid these calls as much as possible because they'll probably won't be available in future. If it's really necessary a new function can be added but only if it's reasonable for overall logic. All query functions were moved to `~/client/entities.py`. Each function has arguments with available filters and possible reduce of returned keys for each entity. + +## Changes +Changes are a little bit complicated. Mongo has many options how update can happen which had to be reduced also it would be at this stage complicated to validate values which are created or updated thus automation is at this point almost none. Changes can be made using operations available in `~/client/operations.py`. Each operation require project name and entity type, but may require operation specific data. + +### Create +Create operations expect already prepared document data, for that are prepared functions creating skeletal structures of documents (do not fill all required data), except `_id` all data should be right. Existence of entity is not validated so if the same creation operation is send n times it will create the entity n times which can cause issues. + +### Update +Update operation require entity id and keys that should be changed, update dictionary must have {"key": value}. If value should be set in nested dictionary the key must have also all subkeys joined with dot `.` (e.g. `{"data": {"fps": 25}}` -> `{"data.fps": 25}`). To simplify update dictionaries were prepared functions which does that for you, their name has template `prepare__update_data` - they work on comparison of previous document and new document. If there is missing function for requested entity type it is because we didn't need it yet and require implementaion. + +### Delete +Delete operation need entity id. Entity will be deleted from mongo. + + +## What (probably) won't be replaced +Some parts of code are still using direct mongo calls. In most of cases it is for very specific calls that are module specific or their usage will completely change in future. +- Mongo calls that are not project specific (out of `avalon` collection) will be removed or will have to use different mechanism how the data are stored. At this moment it is related to OpenPype settings and logs, ftrack server events, some other data. +- Sync server queries. They're complex and very specific for sync server module. Their replacement will require specific calls to OpenPype server in v4 thus their abstraction with wrapper is irrelevant and would complicate production in v3. +- Project managers (ftrack, kitsu, shotgrid, embedded Project Manager, etc.). Project managers are creating, updating or removing assets in v3, but in v4 will create folders with different structure. Wrapping creation of assets would not help to prepare for v4 because of new data structures. The same can be said about editorial Extract Hierarchy Avalon plugin which create project structure. +- Code parts that is marked as deprecated in v3 or will be deprecated in v4. + - integrate asset legacy publish plugin - already is legacy kept for safety + - integrate thumbnail - thumbnails will be stored in different way in v4 + - input links - link will be stored in different way and will have different mechanism of linking. In v3 are links limited to same entity type "asset <-> asset" or "representation <-> representation". + +## Known missing replacements +- change subset group in loader tool +- integrate subset group +- query input links in openpype lib +- create project in openpype lib +- save/create workfile doc in openpype lib +- integrate hero version From ffa3b0829f800f3ee43d7b8e0cf80801dffda591 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Aug 2022 15:12:31 +0200 Subject: [PATCH 094/101] flame: fixing frame ranges after client tests --- .../publish/extract_subset_resources.py | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index 8a03ba119c..3e1e8db986 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -69,6 +69,9 @@ class ExtractSubsetResources(openpype.api.Extractor): # get media source first frame source_first_frame = instance.data["sourceFirstFrame"] + self.log.debug("_ frame_start: {}".format(frame_start)) + self.log.debug("_ source_first_frame: {}".format(source_first_frame)) + # get timeline in/out of segment clip_in = instance.data["clipIn"] clip_out = instance.data["clipOut"] @@ -102,6 +105,25 @@ class ExtractSubsetResources(openpype.api.Extractor): + r_handle_end ) + # get frame range with handles for representation range + frame_start_handle = frame_start - handle_start + repre_frame_start = frame_start_handle + if include_handles: + if r_speed == 1.0: + frame_start_handle = frame_start + else: + frame_start_handle = ( + frame_start - handle_start) + r_handle_start + + self.log.debug("_ frame_start_handle: {}".format( + frame_start_handle)) + self.log.debug("_ repre_frame_start: {}".format( + repre_frame_start)) + + # calculate duration with handles + source_duration_handles = ( + source_end_handles - source_start_handles) + 1 + # create staging dir path staging_dir = self.staging_dir(instance) @@ -120,15 +142,22 @@ class ExtractSubsetResources(openpype.api.Extractor): # set versiondata if any retime version_data = retimed_data.get("version_data") + self.log.debug("_ version_data: {}".format(version_data)) if version_data: instance.data["versionData"].update(version_data) if r_speed != 1.0: instance.data["versionData"].update({ - "frameStart": source_start_handles + r_handle_start, - "frameEnd": source_end_handles - r_handle_end, + "frameStart": frame_start_handle, + "frameEnd": ( + (frame_start_handle + source_duration_handles - 1) + - (r_handle_start + r_handle_end) + ) }) + self.log.debug("_ i_version_data: {}".format( + instance.data["versionData"] + )) # loop all preset names and for unique_name, preset_config in export_presets.items(): @@ -152,22 +181,6 @@ class ExtractSubsetResources(openpype.api.Extractor): ) ) - # get frame range with handles for representation range - frame_start_handle = frame_start - handle_start - if include_handles: - if r_speed == 1.0: - frame_start_handle = frame_start - else: - frame_start_handle = ( - frame_start - handle_start) + r_handle_start - - self.log.debug("_ frame_start_handle: {}".format( - frame_start_handle)) - - # calculate duration with handles - source_duration_handles = ( - source_end_handles - source_start_handles) + 1 - exporting_clip = None name_patern_xml = "_{}.".format( unique_name) @@ -203,7 +216,7 @@ class ExtractSubsetResources(openpype.api.Extractor): modify_xml_data.update({ # enum position low start from 0 "frameIndex": 0, - "startFrame": frame_start_handle, + "startFrame": repre_frame_start, "namePattern": name_patern_xml }) @@ -248,7 +261,7 @@ class ExtractSubsetResources(openpype.api.Extractor): "namePattern": "__thumbnail" }) thumb_frame_number = int(in_mark + ( - source_duration_handles / 2)) + (out_mark - in_mark + 1) / 2)) self.log.debug("__ thumb_frame_number: {}".format( thumb_frame_number @@ -329,9 +342,9 @@ class ExtractSubsetResources(openpype.api.Extractor): # add frame range if preset_config["representation_add_range"]: representation_data.update({ - "frameStart": frame_start_handle, + "frameStart": repre_frame_start, "frameEnd": ( - frame_start_handle + source_duration_handles) - 1, + repre_frame_start + source_duration_handles) - 1, "fps": instance.data["fps"] }) From b96cff6ea9f85f80d5ee801fc8bd17020e484580 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 23 Aug 2022 15:24:58 +0200 Subject: [PATCH 095/101] Removed submodule vendor/configs/OpenColorIO-Configs --- vendor/configs/OpenColorIO-Configs | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs deleted file mode 160000 index 0bb079c08b..0000000000 --- a/vendor/configs/OpenColorIO-Configs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 0d8ab1c17ea388b3f639d1bedab67359463af44c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 15:35:35 +0200 Subject: [PATCH 096/101] fix unsetting of value --- openpype/client/operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/client/operations.py b/openpype/client/operations.py index c4b95bf696..618cdf9d1e 100644 --- a/openpype/client/operations.py +++ b/openpype/client/operations.py @@ -444,7 +444,7 @@ class UpdateOperation(AbstractOperation): set_data = {} for key, value in self._update_data.items(): if value is REMOVED_VALUE: - unset_data[key] = value + unset_data[key] = None else: set_data[key] = value From 0f114331ec45dacc571ab2dc3b04dc869d833d12 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 15:35:57 +0200 Subject: [PATCH 097/101] use client options to change subset group --- openpype/tools/loader/widgets.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 597c35e89b..cbf5720803 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -17,6 +17,7 @@ from openpype.client import ( get_thumbnail_id_from_source, get_thumbnail, ) +from openpype.client.operations import OperationsSession, REMOVED_VALUE from openpype.pipeline import HeroVersionType, Anatomy from openpype.pipeline.thumbnail import get_thumbnail_binary from openpype.pipeline.load import ( @@ -614,26 +615,30 @@ class SubsetWidget(QtWidgets.QWidget): box.show() def group_subsets(self, name, asset_ids, items): - field = "data.subsetGroup" + subset_ids = { + item["_id"] + for item in items + if item.get("_id") + } + if not subset_ids: + return if name: - update = {"$set": {field: name}} self.echo("Group subsets to '%s'.." % name) else: - update = {"$unset": {field: ""}} self.echo("Ungroup subsets..") - subsets = list() - for item in items: - subsets.append(item["subset"]) + project_name = self.dbcon.active_project() + op_session = OperationsSession() + for subset_id in subset_ids: + op_session.update_entity( + project_name, + "subset", + subset_id, + {"data.subsetGroup": name or REMOVED_VALUE} + ) - for asset_id in asset_ids: - filtr = { - "type": "subset", - "parent": asset_id, - "name": {"$in": subsets}, - } - self.dbcon.update_many(filtr, update) + op_session.commit() def echo(self, message): print(message) From 7f234e1d814a92cbe1e446aeef7c71a2a2165163 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 16:20:54 +0200 Subject: [PATCH 098/101] fix iterator index acces --- openpype/tools/loader/model.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 3ce44ea6c8..4f1f37b217 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -272,11 +272,13 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # update availability on active site when version changes if self.sync_server.enabled and version_doc: - repre_info = self.sync_server.get_repre_info_for_versions( - project_name, - [version_doc["_id"]], - self.active_site, - self.remote_site + repre_info = list( + self.sync_server.get_repre_info_for_versions( + project_name, + [version_doc["_id"]], + self.active_site, + self.remote_site + ) ) if repre_info: version_doc["data"].update( From 4062bf56f32a6d5ec3e6bf6ec62c61910b3365bd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 16:36:24 +0200 Subject: [PATCH 099/101] print traceback on crashed dynamic thread --- openpype/tools/utils/lib.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 99d8c75ab4..fb2348518a 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -2,6 +2,7 @@ import os import sys import contextlib import collections +import traceback from Qt import QtWidgets, QtCore, QtGui import qtawesome @@ -643,7 +644,11 @@ class DynamicQThread(QtCore.QThread): def create_qthread(func, *args, **kwargs): class Thread(QtCore.QThread): def run(self): - func(*args, **kwargs) + try: + func(*args, **kwargs) + except: + traceback.print_exception(*sys.exc_info()) + raise return Thread() From fce4e6e3d8f5c3d7f2b929a0328324ce1d951e9b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 16:38:45 +0200 Subject: [PATCH 100/101] fix version specific repre info in loader --- openpype/tools/loader/model.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 4f1f37b217..929e497890 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -272,7 +272,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # update availability on active site when version changes if self.sync_server.enabled and version_doc: - repre_info = list( + repres_info = list( self.sync_server.get_repre_info_for_versions( project_name, [version_doc["_id"]], @@ -280,9 +280,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.remote_site ) ) - if repre_info: + if repres_info: version_doc["data"].update( - self._get_repre_dict(repre_info[0])) + self._get_repre_dict(repres_info[0])) self.set_version(index, version_doc) @@ -474,29 +474,34 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version - repre_info = {} + repre_info_by_version_id = {} if self.sync_server.enabled: - version_ids = set() + versions_by_id = {} for _subset_id, doc in last_versions_by_subset_id.items(): - version_ids.add(doc["_id"]) + versions_by_id[doc["_id"]] = doc - repres = self.sync_server.get_repre_info_for_versions( + repres_info = self.sync_server.get_repre_info_for_versions( project_name, - list(version_ids), self.active_site, self.remote_site + list(versions_by_id.keys()), + self.active_site, + self.remote_site ) - for repre in repres: + for repre_info in repres_info: if self._doc_fetching_stop: return + + version_id = repre_info["_id"] + doc = versions_by_id[version_id] doc["active_provider"] = self.active_provider doc["remote_provider"] = self.remote_provider - repre_info[repre["_id"]] = repre + repre_info_by_version_id[version_id] = repre_info self._doc_payload = { "asset_docs_by_id": asset_docs_by_id, "subset_docs_by_id": subset_docs_by_id, "subset_families": subset_families, "last_versions_by_subset_id": last_versions_by_subset_id, - "repre_info_by_version_id": repre_info + "repre_info_by_version_id": repre_info_by_version_id } self.doc_fetched.emit() From c393105e2502ed4b9dba95aa2a49cefbd849c2a0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 23 Aug 2022 16:39:31 +0200 Subject: [PATCH 101/101] use BaseException --- openpype/tools/utils/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index fb2348518a..97b680b77e 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -646,7 +646,7 @@ def create_qthread(func, *args, **kwargs): def run(self): try: func(*args, **kwargs) - except: + except BaseException: traceback.print_exception(*sys.exc_info()) raise return Thread()