From 08e8f6016193002979af8ed090d02634ca1c1db5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:04:28 +0200 Subject: [PATCH 01/47] list entity can use templates or schemas --- openpype/settings/entities/list_entity.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 64bbad28a7..ce200862f6 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -141,7 +141,21 @@ class ListEntity(EndpointEntity): item_schema = self.schema_data["object_type"] if not isinstance(item_schema, dict): item_schema = {"type": item_schema} - self.item_schema = item_schema + + schema_template_used = False + _item_schemas = self.schema_hub.resolve_schema_data(item_schema) + if len(_item_schemas) == 1: + self.item_schema = _item_schemas[0] + if self.item_schema != item_schema: + schema_template_used = True + if "label" in self.item_schema: + self.item_schema.pop("label") + self.item_schema["use_label_wrap"] = False + else: + self.item_schema = _item_schemas + + # Store if was used template or schema + self._schema_template_used = schema_template_used if self.group_item is None: self.is_group = True From ff7ccfecba187237465151d1fbd6f5024973ef93 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:07:35 +0200 Subject: [PATCH 02/47] validate children on schema validations only if was not used from schema --- openpype/settings/entities/list_entity.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index ce200862f6..4a2b5968d9 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -187,7 +187,11 @@ class ListEntity(EndpointEntity): child_validated = True break - if not child_validated: + # Do not validate if was used schema or template + # - that is validated on first created children + # - it is because template or schema can use itself inside children + # TODO Do validations maybe store to `schema_hub` what is validated + if not self._schema_template_used and not child_validated: idx = 0 tmp_child = self._add_new_item(idx) tmp_child.schema_validations() From 8b789df5ec7beaf19b8d473c1012949ff294fc84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:07:58 +0200 Subject: [PATCH 03/47] validate if item_schema is list --- openpype/settings/entities/list_entity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 4a2b5968d9..b12e6d8f5c 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -164,6 +164,12 @@ class ListEntity(EndpointEntity): self.initial_value = [] def schema_validations(self): + if isinstance(self.item_schema, list): + reason = ( + "`ListWidget` has multiple items as object type." + ) + raise EntitySchemaError(self, reason) + super(ListEntity, self).schema_validations() if self.is_dynamic_item and self.use_label_wrap: From 8f6e8b19885e836a8adf3892659d282909f1cbf0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:08:09 +0200 Subject: [PATCH 04/47] handle child validations --- openpype/settings/entities/list_entity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index b12e6d8f5c..e89c7cadec 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -94,6 +94,12 @@ class ListEntity(EndpointEntity): def _add_new_item(self, idx=None): child_obj = self.create_schema_object(self.item_schema, self, True) + + # Validate child if was not validated yet + if not self._child_validated: + child_obj.schema_validations() + self._child_validated = True + if idx is None: self.children.append(child_obj) else: @@ -156,6 +162,8 @@ class ListEntity(EndpointEntity): # Store if was used template or schema self._schema_template_used = schema_template_used + # Store if child was validated + self._child_validated = False if self.group_item is None: self.is_group = True @@ -202,6 +210,9 @@ class ListEntity(EndpointEntity): tmp_child = self._add_new_item(idx) tmp_child.schema_validations() self.children.pop(idx) + child_validated = True + + self._child_validated = child_validated def get_child_path(self, child_obj): result_idx = None From 0356e60faf83c48db881cf94ea21e6fa8a482b99 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:31:34 +0200 Subject: [PATCH 05/47] get template name from item --- openpype/settings/entities/lib.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index e58281644a..dee80c09aa 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -126,6 +126,22 @@ class SchemasHub: def gui_types(self): return self._gui_types + def get_template_name(self, item_def, default=None): + """Get template name from passed item definition. + + Args: + item_def(dict): Definition of item with "type". + default(object): Default return value. + """ + output = default + if not item_def or not isinstance(item_def, dict): + return output + + item_type = item_def.get("type") + if item_type in ("template", "schema_template"): + output = item_def["name"] + return output + def get_schema(self, schema_name): """Get schema definition data by it's name. From 41218b61ec79a5ad7dc68a42f636a44562c4cef5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:32:13 +0200 Subject: [PATCH 06/47] added validation methods and variables --- openpype/settings/entities/lib.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index dee80c09aa..1c4a51b7c9 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -111,6 +111,10 @@ class SchemasHub: self._loaded_templates = {} self._loaded_schemas = {} + # Store validating and validated dynamic template or schemas + self._validating_dynamic = set() + self._validated_dynamic = set() + # It doesn't make sence to reload types on each reset as they can't be # changed self._load_types() @@ -142,6 +146,27 @@ class SchemasHub: output = item_def["name"] return output + def is_dynamic_template_validating(self, template_name): + """Is template validating using different entity. + + Returns: + bool: Is template validating. + """ + if template_name in self._validating_dynamic: + return True + return False + + def is_dynamic_template_validated(self, template_name): + """Is template already validated. + + Returns: + bool: Is template validated. + """ + + if template_name in self._validated_dynamic: + return True + return False + def get_schema(self, schema_name): """Get schema definition data by it's name. From 027cb48a13e718dfcdb0c25327ddba4fbed677d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:36:54 +0200 Subject: [PATCH 07/47] added context manager method for using validation of dynamic template --- openpype/settings/entities/lib.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 1c4a51b7c9..01f61d8bdf 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -3,6 +3,7 @@ import re import json import copy import inspect +import contextlib from .exceptions import ( SchemaTemplateMissingKeys, @@ -167,6 +168,23 @@ class SchemasHub: return True return False + @contextlib.contextmanager + def validating_dynamic(self, template_name): + """Template name is validating and validated. + + Context manager that cares about storing template name validations of + template. + + This is to avoid infinite loop of dynamic children validation. + """ + self._validating_dynamic.add(template_name) + try: + yield + self._validated_dynamic.add(template_name) + + finally: + self._validating_dynamic.remove(template_name) + def get_schema(self, schema_name): """Get schema definition data by it's name. From 148e1a9564c421fabcd31349aac1a146db2c70e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:37:15 +0200 Subject: [PATCH 08/47] better children validation of dynamic templates --- openpype/settings/entities/list_entity.py | 42 ++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index e89c7cadec..b07441251a 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -94,12 +94,6 @@ class ListEntity(EndpointEntity): def _add_new_item(self, idx=None): child_obj = self.create_schema_object(self.item_schema, self, True) - - # Validate child if was not validated yet - if not self._child_validated: - child_obj.schema_validations() - self._child_validated = True - if idx is None: self.children.append(child_obj) else: @@ -148,12 +142,11 @@ class ListEntity(EndpointEntity): if not isinstance(item_schema, dict): item_schema = {"type": item_schema} - schema_template_used = False + obj_template_name = self.schema_hub.get_template_name(item_schema) _item_schemas = self.schema_hub.resolve_schema_data(item_schema) if len(_item_schemas) == 1: self.item_schema = _item_schemas[0] if self.item_schema != item_schema: - schema_template_used = True if "label" in self.item_schema: self.item_schema.pop("label") self.item_schema["use_label_wrap"] = False @@ -161,9 +154,7 @@ class ListEntity(EndpointEntity): self.item_schema = _item_schemas # Store if was used template or schema - self._schema_template_used = schema_template_used - # Store if child was validated - self._child_validated = False + self._obj_template_name = obj_template_name if self.group_item is None: self.is_group = True @@ -195,24 +186,35 @@ class ListEntity(EndpointEntity): raise EntitySchemaError(self, reason) # Validate object type schema - child_validated = False + validate_children = True for child_entity in self.children: child_entity.schema_validations() - child_validated = True + validate_children = False break - # Do not validate if was used schema or template - # - that is validated on first created children - # - it is because template or schema can use itself inside children - # TODO Do validations maybe store to `schema_hub` what is validated - if not self._schema_template_used and not child_validated: + if validate_children and self._obj_template_name: + _validated = self.schema_hub.is_dynamic_template_validated( + self._obj_template_name + ) + _validating = self.schema_hub.is_dynamic_template_validating( + self._obj_template_name + ) + validate_children = not _validated and not _validating + + if not validate_children: + return + + def _validate(): idx = 0 tmp_child = self._add_new_item(idx) tmp_child.schema_validations() self.children.pop(idx) - child_validated = True - self._child_validated = child_validated + if self._obj_template_name: + with self.schema_hub.validating_dynamic(self._obj_template_name): + _validate() + else: + _validate() def get_child_path(self, child_obj): result_idx = None From 858e46d0f63e3e2e2575935f9e4a673d6932b7af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 13:53:58 +0200 Subject: [PATCH 09/47] added description and example --- openpype/settings/entities/schemas/README.md | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 3c360b892f..e098198c2c 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -404,6 +404,8 @@ How output of the schema could look like on save: - 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 ``` @@ -429,6 +431,63 @@ How output of the schema could look like on save: } ``` +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`). +[ + { + "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 From e97d14634535bed8aa2daaa4a3078dc6bf252427 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Jul 2021 14:50:38 +0200 Subject: [PATCH 10/47] added more specific name of template filename --- openpype/settings/entities/schemas/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 02e3e0a83c..8760187038 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -452,6 +452,8 @@ 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`). + +'template_object_example.json' : [ { "type": "dict-conditional", From 5c6b15b30aaf73e7cf7ec07e14bd0703388d12e2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Jul 2021 10:32:17 +0200 Subject: [PATCH 11/47] skip entities that are None after query --- .../modules/ftrack/lib/ftrack_base_handler.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openpype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py index 011ce8db9d..cfe55014a8 100644 --- a/openpype/modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py @@ -182,15 +182,22 @@ class BaseHandler(object): if session is None: session = self.session - _entities = event['data'].get('entities_object', None) + _entities = event["data"].get("entities_object", None) + if _entities is not None and not _entities: + return _entities + if ( - _entities is None or - _entities[0].get( - 'link', None + _entities is None + or _entities[0].get( + "link", None ) == ftrack_api.symbol.NOT_SET ): - _entities = self._get_entities(event) - event['data']['entities_object'] = _entities + _entities = [ + item + for item in self._get_entities(event) + if item is not None + ] + event["data"]["entities_object"] = _entities return _entities From 12063d2b5ff38f7d3d900f8d41d8c839be80120d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Jul 2021 19:16:40 +0200 Subject: [PATCH 12/47] added example of using template as object type in list --- .../example_infinite_hierarchy.json | 58 +++++++++++++++++++ .../schemas/system_schema/example_schema.json | 11 ++++ 2 files changed, 69 insertions(+) create mode 100644 openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json diff --git a/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json b/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json new file mode 100644 index 0000000000..a2660e9bf2 --- /dev/null +++ b/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json @@ -0,0 +1,58 @@ +[ + { + "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" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": { + "type": "template", + "name": "example_infinite_hierarchy" + } + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] + } +] diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index c3287d7452..71a15ca721 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -57,6 +57,17 @@ } ] }, + { + "type": "list", + "use_label_wrap": true, + "collapsible": true, + "key": "infinite_hierarchy", + "label": "Infinite list template hierarchy", + "object_type": { + "type": "template", + "name": "example_infinite_hierarchy" + } + }, { "type": "dict", "key": "schema_template_exaples", From 0e2bf5f522b4668cbd0dc43cc54efb3c97737696 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 3 Aug 2021 17:49:21 +0200 Subject: [PATCH 13/47] compute environments after merge envs --- openpype/lib/applications.py | 8 ++++++-- start.py | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index ada194f15f..9f5a092afc 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1138,7 +1138,8 @@ def prepare_host_environments(data, implementation_envs=True): # Merge dictionaries env_values = _merge_env(tool_env, env_values) - loaded_env = _merge_env(acre.compute(env_values), data["env"]) + merged_env = _merge_env(computed_env, data["env"]) + loaded_env = acre.compute(merged_env, cleanup=False) final_env = None # Add host specific environments @@ -1189,7 +1190,10 @@ def apply_project_environments_value(project_name, env, project_settings=None): env_value = project_settings["global"]["project_environments"] if env_value: - env.update(_merge_env(acre.parse(env_value), env)) + env.update(acre.compute( + _merge_env(acre.parse(env_value), env), + cleanup=False + )) return env diff --git a/start.py b/start.py index 419a956835..6473a926d0 100644 --- a/start.py +++ b/start.py @@ -221,10 +221,14 @@ def set_openpype_global_environments() -> None: all_env = get_environments() general_env = all_env["global"] - env = acre.merge( + merged_env = acre.merge( acre.parse(general_env), dict(os.environ) ) + env = acre.compute( + merged_env, + cleanup=False + ) os.environ.clear() os.environ.update(env) From 6c0283f9ab9e70daf3fda03f2359b4dec11bf090 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 3 Aug 2021 17:54:56 +0200 Subject: [PATCH 14/47] fix variable --- openpype/lib/applications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 9f5a092afc..fe964d3bab 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1138,7 +1138,7 @@ def prepare_host_environments(data, implementation_envs=True): # Merge dictionaries env_values = _merge_env(tool_env, env_values) - merged_env = _merge_env(computed_env, data["env"]) + merged_env = _merge_env(env_values, data["env"]) loaded_env = acre.compute(merged_env, cleanup=False) final_env = None From b19b38a925e80e2d9d4fdf2bdb63ee52e7e02a15 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 4 Aug 2021 13:55:27 +0200 Subject: [PATCH 15/47] updated acre commit --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index aad1898983..e011b781c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,7 +11,7 @@ develop = false type = "git" url = "https://github.com/pypeclub/acre.git" reference = "master" -resolved_reference = "5a812c6dcfd3aada87adb49be98c548c894d6566" +resolved_reference = "55a7c331e6dc5f81639af50ca4a8cc9d73e9273d" [[package]] name = "aiohttp" From 550b8f38da42a767ee35f4d54e9987257f9e8241 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 4 Aug 2021 17:40:09 +0200 Subject: [PATCH 16/47] added action which helps to identify if actions are shown on private project where ftrack event server does not have access --- .../action_private_project_detection.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py diff --git a/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py b/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py new file mode 100644 index 0000000000..5213e10ba3 --- /dev/null +++ b/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py @@ -0,0 +1,61 @@ +from openpype.modules.ftrack.lib import ServerAction + + +class PrivateProjectDetectionAction(ServerAction): + """Action helps to identify if does not have access to project.""" + + identifier = "server.missing.perm.private.project" + label = "Missing permissions" + description = ( + "Main ftrack event server does not have access to this project." + ) + + def _discover(self, event): + """Show action only if there is a selection in event data.""" + entities = self._translate_event(event) + if entities: + return None + + selection = event["data"].get("selection") + if not selection: + return None + + return { + "items": [{ + "label": self.label, + "variant": self.variant, + "description": self.description, + "actionIdentifier": self.discover_identifier, + "icon": self.icon, + }] + } + + def _launch(self, event): + # Ignore if there are values in event data + # - somebody clicked on submit button + values = event["data"].get("values") + if values: + return None + + title = "# Private project (missing permissions) #" + msg = ( + "User ({}) or API Key used on Ftrack event server" + " does not have permissions to access this private project." + ).format(self.session.api_user) + return { + "type": "form", + "title": "Missing permissions", + "items": [ + {"type": "label", "value": title}, + {"type": "label", "value": msg}, + # Add hidden to be able detect if was clicked on submit + {"type": "hidden", "value": "1", "name": "hidden"} + ], + "submit_button_label": "Got it" + } + + +def register(session): + '''Register plugin. Called when used as an plugin.''' + + PrivateProjectDetectionAction(session).register() From 83a2c0ff0481b0a2bc143a96dba471cbda6d63c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 4 Aug 2021 18:06:39 +0200 Subject: [PATCH 17/47] merged where i run actions into one --- .../action_where_run_ask.py | 105 ++++++++++++++---- .../action_where_run_show.py | 86 -------------- 2 files changed, 85 insertions(+), 106 deletions(-) delete mode 100644 openpype/modules/ftrack/event_handlers_user/action_where_run_show.py diff --git a/openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py b/openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py index 6950d45ecd..2c427cfff7 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py +++ b/openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py @@ -1,33 +1,98 @@ +import platform +import socket +import getpass + from openpype.modules.ftrack.lib import BaseAction, statics_icon -class ActionAskWhereIRun(BaseAction): - """ Sometimes user forget where pipeline with his credentials is running. - - this action triggers `ActionShowWhereIRun` - """ - ignore_me = True - identifier = 'ask.where.i.run' - label = 'Ask where I run' - description = 'Triggers PC info where user have running OpenPype' - icon = statics_icon("ftrack", "action_icons", "ActionAskWhereIRun.svg") +class ActionWhereIRun(BaseAction): + """Show where same user has running OpenPype instances.""" - def discover(self, session, entities, event): - """ Hide by default - Should be enabled only if you want to run. - - best practise is to create another action that triggers this one - """ + identifier = "ask.where.i.run" + show_identifier = "show.where.i.run" + label = "OpenPype Admin" + variant = "- Where I run" + description = "Show PC info where user have running OpenPype" - return True + def _discover(self, _event): + return { + "items": [{ + "label": self.label, + "variant": self.variant, + "description": self.description, + "actionIdentifier": self.discover_identifier, + "icon": self.icon, + }] + } - def launch(self, session, entities, event): - more_data = {"event_hub_id": session.event_hub.id} - self.trigger_action( - "show.where.i.run", event, additional_event_data=more_data + def _launch(self, event): + self.trigger_action(self.show_identifier, event) + + def register(self): + # Register default action callbacks + super(ActionWhereIRun, self).register() + + # Add show identifier + show_subscription = ( + "topic=ftrack.action.launch" + " and data.actionIdentifier={}" + " and source.user.username={}" + ).format( + self.show_identifier, + self.session.api_user + ) + self.session.event_hub.subscribe( + show_subscription, + self._show_info ) - return True + def _show_info(self, event): + title = "Where Do I Run?" + msgs = {} + all_keys = ["Hostname", "IP", "Username", "System name", "PC name"] + try: + host_name = socket.gethostname() + msgs["Hostname"] = host_name + host_ip = socket.gethostbyname(host_name) + msgs["IP"] = host_ip + except Exception: + pass + + try: + system_name, pc_name, *_ = platform.uname() + msgs["System name"] = system_name + msgs["PC name"] = pc_name + except Exception: + pass + + try: + msgs["Username"] = getpass.getuser() + except Exception: + pass + + for key in all_keys: + if not msgs.get(key): + msgs[key] = "-Undefined-" + + items = [] + first = True + separator = {"type": "label", "value": "---"} + for key, value in msgs.items(): + if first: + first = False + else: + items.append(separator) + self.log.debug("{}: {}".format(key, value)) + + subtitle = {"type": "label", "value": "

{}

".format(key)} + items.append(subtitle) + message = {"type": "label", "value": "

{}

".format(value)} + items.append(message) + + self.show_interface(items, title, event=event) def register(session): '''Register plugin. Called when used as an plugin.''' - ActionAskWhereIRun(session).register() + ActionWhereIRun(session).register() diff --git a/openpype/modules/ftrack/event_handlers_user/action_where_run_show.py b/openpype/modules/ftrack/event_handlers_user/action_where_run_show.py deleted file mode 100644 index b8b49e86cb..0000000000 --- a/openpype/modules/ftrack/event_handlers_user/action_where_run_show.py +++ /dev/null @@ -1,86 +0,0 @@ -import platform -import socket -import getpass -from openpype.modules.ftrack.lib import BaseAction - - -class ActionShowWhereIRun(BaseAction): - """ Sometimes user forget where pipeline with his credentials is running. - - this action shows on which PC, Username and IP is running - - requirement action MUST be registered where we want to locate the PC: - - - can't be used retrospectively... - """ - #: Action identifier. - identifier = 'show.where.i.run' - #: Action label. - label = 'Show where I run' - #: Action description. - description = 'Shows PC info where user have running OpenPype' - - def discover(self, session, entities, event): - """ Hide by default - Should be enabled only if you want to run. - - best practise is to create another action that triggers this one - """ - - return False - - @property - def launch_identifier(self): - return self.identifier - - def launch(self, session, entities, event): - # Don't show info when was launch from this session - if session.event_hub.id == event.get("data", {}).get("event_hub_id"): - return True - - title = "Where Do I Run?" - msgs = {} - all_keys = ["Hostname", "IP", "Username", "System name", "PC name"] - try: - host_name = socket.gethostname() - msgs["Hostname"] = host_name - host_ip = socket.gethostbyname(host_name) - msgs["IP"] = host_ip - except Exception: - pass - - try: - system_name, pc_name, *_ = platform.uname() - msgs["System name"] = system_name - msgs["PC name"] = pc_name - except Exception: - pass - - try: - msgs["Username"] = getpass.getuser() - except Exception: - pass - - for key in all_keys: - if not msgs.get(key): - msgs[key] = "-Undefined-" - - items = [] - first = True - splitter = {'type': 'label', 'value': '---'} - for key, value in msgs.items(): - if first: - first = False - else: - items.append(splitter) - self.log.debug("{}: {}".format(key, value)) - - subtitle = {'type': 'label', 'value': '

{}

'.format(key)} - items.append(subtitle) - message = {'type': 'label', 'value': '

{}

'.format(value)} - items.append(message) - - self.show_interface(items, title, event=event) - - return True - - -def register(session): - '''Register plugin. Called when used as an plugin.''' - - ActionShowWhereIRun(session).register() From cb7d8704e60580d949225c5f3e0abea1067487e9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Aug 2021 20:21:08 +0200 Subject: [PATCH 18/47] Moved Deadline settings from Global to Deadline plugin --- .../defaults/project_settings/deadline.json | 24 +++++ .../defaults/project_settings/global.json | 32 ++----- .../schema_project_deadline.json | 95 +++++++++++++++++++ .../schemas/schema_global_publish.json | 95 ------------------- 4 files changed, 129 insertions(+), 117 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 2dba20d63c..0f2da9f5b0 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -11,6 +11,30 @@ "deadline" ] }, + "ProcessSubmittedJobOnFarm": { + "enabled": true, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "", + "deadline_chunk_size": 1, + "deadline_priority": 50, + "publishing_script": "", + "skip_integration_repre_list": [], + "aov_filter": { + "maya": [ + ".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*" + ], + "nuke": [ + ".*" + ], + "aftereffects": [ + ".*" + ], + "celaction": [ + ".*" + ] + } + }, "MayaSubmitDeadline": { "enabled": true, "optional": false, diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index c14486f384..aab8c2196c 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -173,28 +173,6 @@ } ] }, - "ProcessSubmittedJobOnFarm": { - "enabled": true, - "deadline_department": "", - "deadline_pool": "", - "deadline_group": "", - "deadline_chunk_size": 1, - "deadline_priority": 50, - "publishing_script": "", - "skip_integration_repre_list": [], - "aov_filter": { - "maya": [ - ".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*" - ], - "nuke": [], - "aftereffects": [ - ".*" - ], - "celaction": [ - ".*" - ] - } - }, "CleanUp": { "paterns": [], "remove_temp_renders": false @@ -257,6 +235,16 @@ ], "tasks": [], "template": "{family}{Task}" + }, + { + "families": [ + "renderLocal" + ], + "hosts": [ + "aftereffects" + ], + "tasks": [], + "template": "render{Task}{Variant}" } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 27eeaef559..8e6a4b10e4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -52,6 +52,101 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ProcessSubmittedJobOnFarm", + "label": "ProcessSubmittedJobOnFarm", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "deadline_department", + "label": "Deadline department" + }, + { + "type": "text", + "key": "deadline_pool", + "label": "Deadline Pool" + }, + { + "type": "text", + "key": "deadline_group", + "label": "Deadline Group" + }, + { + "type": "number", + "key": "deadline_chunk_size", + "label": "Deadline Chunk Size" + }, + { + "type": "number", + "key": "deadline_priority", + "label": "Deadline Priotity" + }, + { + "type": "splitter" + }, + { + "type": "text", + "key": "publishing_script", + "label": "Publishing script path" + }, + { + "type": "list", + "key": "skip_integration_repre_list", + "label": "Skip integration of representation with ext", + "object_type": { + "type": "text" + } + }, + { + "type": "dict", + "key": "aov_filter", + "label": "Reviewable subsets filter", + "children": [ + { + "type": "list", + "key": "maya", + "label": "Maya", + "object_type": { + "type": "text" + } + }, + { + "type": "list", + "key": "nuke", + "label": "Nuke", + "object_type": { + "type": "text" + } + }, + { + "type": "list", + "key": "aftereffects", + "label": "After Effects", + "object_type": { + "type": "text" + } + }, + { + "type": "list", + "key": "celaction", + "label": "Celaction", + "object_type": { + "type": "text" + } + } + ] + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index a1cbc8639f..d265988534 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -556,101 +556,6 @@ } ] }, - { - "type": "dict", - "collapsible": true, - "key": "ProcessSubmittedJobOnFarm", - "label": "ProcessSubmittedJobOnFarm", - "checkbox_key": "enabled", - "is_group": true, - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "text", - "key": "deadline_department", - "label": "Deadline department" - }, - { - "type": "text", - "key": "deadline_pool", - "label": "Deadline Pool" - }, - { - "type": "text", - "key": "deadline_group", - "label": "Deadline Group" - }, - { - "type": "number", - "key": "deadline_chunk_size", - "label": "Deadline Chunk Size" - }, - { - "type": "number", - "key": "deadline_priority", - "label": "Deadline Priotity" - }, - { - "type": "splitter" - }, - { - "type": "text", - "key": "publishing_script", - "label": "Publishing script path" - }, - { - "type": "list", - "key": "skip_integration_repre_list", - "label": "Skip integration of representation with ext", - "object_type": { - "type": "text" - } - }, - { - "type": "dict", - "key": "aov_filter", - "label": "Reviewable subsets filter", - "children": [ - { - "type": "list", - "key": "maya", - "label": "Maya", - "object_type": { - "type": "text" - } - }, - { - "type": "list", - "key": "nuke", - "label": "Nuke", - "object_type": { - "type": "text" - } - }, - { - "type": "list", - "key": "aftereffects", - "label": "After Effects", - "object_type": { - "type": "text" - } - }, - { - "type": "list", - "key": "celaction", - "label": "Celaction", - "object_type": { - "type": "text" - } - } - ] - } - ] - }, { "type": "dict", "collapsible": true, From 9585240a2b3c1fe67fe372be59332a522db92e63 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Aug 2021 20:21:48 +0200 Subject: [PATCH 19/47] #122 - AE local - added ftrack family --- .../settings/defaults/project_settings/ftrack.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index dae5a591e9..9fa78ac588 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -298,6 +298,17 @@ "add_ftrack_family": true } ] + }, + { + "hosts": [ + "aftereffects" + ], + "families": [ + "render" + ], + "tasks": [], + "add_ftrack_family": true, + "advanced_filtering": [] } ] }, From 5ee187424d6c6e67e2ed5308cdf9ce22733cd67a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Aug 2021 20:22:24 +0200 Subject: [PATCH 20/47] #122 - AE local - added local creator --- .../plugins/create/create_local_render.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 openpype/hosts/aftereffects/plugins/create/create_local_render.py diff --git a/openpype/hosts/aftereffects/plugins/create/create_local_render.py b/openpype/hosts/aftereffects/plugins/create/create_local_render.py new file mode 100644 index 0000000000..9cc06eb698 --- /dev/null +++ b/openpype/hosts/aftereffects/plugins/create/create_local_render.py @@ -0,0 +1,17 @@ +from openpype.hosts.aftereffects.plugins.create import create_render + +import logging + +log = logging.getLogger(__name__) + + +class CreateLocalRender(create_render.CreateRender): + """ Creator to render locally. + + Created only after default render on farm. So family 'render.local' is + used for backward compatibility. + """ + + name = "renderDefault" + label = "Render Locally" + family = "renderLocal" From dfe538ae2630e68967624791e666e1715e083c5f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Aug 2021 20:24:01 +0200 Subject: [PATCH 21/47] #122 - AE local - added local render functionality Added local render extract plugin Updated validator to trigger on local render too Updated current Deadline collector for local render --- .../plugins/publish/collect_render.py | 53 +++++++++++--- .../plugins/publish/extract_local_render.py | 69 +++++++++++++++++++ .../publish/validate_scene_settings.py | 2 +- 3 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/aftereffects/plugins/publish/extract_local_render.py diff --git a/openpype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py index baac64ed0c..be024b7e24 100644 --- a/openpype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -1,10 +1,14 @@ -from openpype.lib import abstract_collect_render -from openpype.lib.abstract_collect_render import RenderInstance -import pyblish.api -import attr import os +import re +import attr +import tempfile from avalon import aftereffects +import pyblish.api + +from openpype.settings import get_project_settings +from openpype.lib import abstract_collect_render +from openpype.lib.abstract_collect_render import RenderInstance @attr.s @@ -13,6 +17,8 @@ class AERenderInstance(RenderInstance): comp_name = attr.ib(default=None) comp_id = attr.ib(default=None) fps = attr.ib(default=None) + projectEntity = attr.ib(default=None) + stagingDir = attr.ib(default=None) class CollectAERender(abstract_collect_render.AbstractCollectRender): @@ -21,6 +27,11 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): label = "Collect After Effects Render Layers" hosts = ["aftereffects"] + # internal + family_remapping = { + "render": ("render.farm", "farm"), # (family, label) + "renderLocal": ("render", "local") + } padding_width = 6 rendered_extension = 'png' @@ -62,14 +73,16 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): fps = work_area_info.frameRate # TODO add resolution when supported by extension - if inst["family"] == "render" and inst["active"]: + if inst["family"] in self.family_remapping.keys() \ + and inst["active"]: + remapped_family = self.family_remapping[inst["family"]] instance = AERenderInstance( - family="render.farm", # other way integrate would catch it - families=["render.farm"], + family=remapped_family[0], + families=[remapped_family[0]], version=version, time="", source=current_file, - label="{} - farm".format(inst["subset"]), + label="{} - {}".format(inst["subset"], remapped_family[1]), subset=inst["subset"], asset=context.data["assetEntity"]["name"], attachTo=False, @@ -105,6 +118,30 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): instance.outputDir = self._get_output_dir(instance) + settings = get_project_settings(os.getenv("AVALON_PROJECT")) + reviewable_subset_filter = \ + (settings["deadline"] + ["publish"] + ["ProcessSubmittedJobOnFarm"] + ["aov_filter"]) + + if inst["family"] == "renderLocal": + # for local renders + instance.anatomyData["version"] = instance.version + instance.anatomyData["subset"] = instance.subset + instance.stagingDir = tempfile.mkdtemp() + instance.projectEntity = project_entity + + if self.hosts[0] in reviewable_subset_filter.keys(): + for aov_pattern in \ + reviewable_subset_filter[self.hosts[0]]: + if re.match(aov_pattern, instance.subset): + instance.families.append("review") + instance.review = True + break + + self.log.info("New instance:: {}".format(instance)) + instances.append(instance) return instances diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py new file mode 100644 index 0000000000..0f82961bdf --- /dev/null +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -0,0 +1,69 @@ +import os + +import openpype.api +from avalon import aftereffects + + +class ExtractLocalRender(openpype.api.Extractor): + """Render RenderQueue locally.""" + + order = openpype.api.Extractor.order - 0.47 + label = "Extract Local Render" + hosts = ["aftereffects"] + families = ["render"] + + def process(self, instance): + stub = aftereffects.stub() + staging_dir = instance.data["stagingDir"] + + stub.render(staging_dir) + + # pull file name from Render Queue Output module + render_q = stub.get_render_info() + if not render_q: + raise ValueError("No file extension set in Render Queue") + _, ext = os.path.splitext(os.path.basename(render_q.file_name)) + ext = ext[1:] + + first_file_path = None + files = [] + for file_name in os.listdir(staging_dir): + files.append(file_name) + if first_file_path is None: + first_file_path = os.path.join(staging_dir, + file_name) + + repre_data = { + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "name": ext, + "ext": ext, + "files": files, + "stagingDir": staging_dir + } + if instance.data["review"]: + repre_data["preview"] = True, + repre_data["tags"] = ["review", "ftrackreview"] + + instance.data["representations"] = [repre_data] + + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + # Generate thumbnail. + thumbnail_path = os.path.join(staging_dir, + "thumbnail.jpg") + args = [ + ffmpeg_path, "-y", + "-i", first_file_path, + "-vf", "scale=300:-1", + "-vframes", "1", + thumbnail_path + ] + output = openpype.lib.run_subprocess(args) + + instance.data["representations"].append({ + "name": "thumbnail", + "ext": "jpg", + "files": os.path.basename(thumbnail_path), + "stagingDir": staging_dir, + "tags": ["thumbnail"] + }) diff --git a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py index 5301a2f3ea..7fba11957c 100644 --- a/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/aftereffects/plugins/publish/validate_scene_settings.py @@ -53,7 +53,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Scene Settings" - families = ["render.farm"] + families = ["render.farm", "render"] hosts = ["aftereffects"] optional = True From ce6067c883d79bf678fdef2ceb9dbbd8dff83ab6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 5 Aug 2021 09:18:28 +0200 Subject: [PATCH 22/47] #122 - AE local - added local render functionality to documentation --- website/docs/artist_hosts_aftereffects.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index 879c0d4646..a9026ce2d4 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -22,7 +22,7 @@ Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension w ## Implemented functionality AfterEffects implementation currently allows you to import and add various media to composition (image plates, renders, audio files, video files etc.) -and send prepared composition for rendering to Deadline. +and send prepared composition for rendering to Deadline or render locally. ## Usage @@ -53,6 +53,10 @@ will be changed. ### Publish +#### RenderQueue + +AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue. Currently its expected to have only single render item and single output module in the Render Queue. + When you are ready to share your work, you will need to publish it. This is done by opening the `Publish` by clicking the corresponding button in the OpenPype Panel. ![Publish](assets/aftereffects_publish.png) From 1a1559004157d94d717acd580b3013f02cc2bc47 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 5 Aug 2021 09:26:35 +0200 Subject: [PATCH 23/47] #122 - AE local - added local render functionality to documentation --- website/docs/artist_hosts_aftereffects.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index a9026ce2d4..fffc6302b7 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -55,7 +55,9 @@ will be changed. #### RenderQueue -AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue. Currently its expected to have only single render item and single output module in the Render Queue. +AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module. Currently its expected to have only single render item and single output module in the Render Queue. + +AE might throw some warning windows during publishing locally, so please pay attention to them in a case publishing seems to be stuck in a `Extract Local Render`. When you are ready to share your work, you will need to publish it. This is done by opening the `Publish` by clicking the corresponding button in the OpenPype Panel. From 13197d01d7e37706ddff2489a33c71a9b4a358a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 5 Aug 2021 10:45:17 +0200 Subject: [PATCH 24/47] h264 cares about bitrate --- openpype/scripts/otio_burnin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index ca77171981..dc8d60cb37 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -113,6 +113,10 @@ def _h264_codec_args(ffprobe_data): output.extend(["-codec:v", "h264"]) + bit_rate = ffprobe_data.get("bit_rate") + if bit_rate: + output.extend(["-b:v", bit_rate]) + pix_fmt = ffprobe_data.get("pix_fmt") if pix_fmt: output.extend(["-pix_fmt", pix_fmt]) From 9fa69dd43c51f1c41bf1c8a259ec7812cdd9c331 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 Aug 2021 12:16:32 +0200 Subject: [PATCH 25/47] Hiero: loaded clip was not set colorspace from version data --- openpype/hosts/hiero/plugins/load/load_clip.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 9e12fa360e..fa666207c5 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -54,6 +54,9 @@ class LoadClip(phiero.SequenceLoader): object_name = self.clip_name_template.format( **context["representation"]["context"]) + # set colorspace + track_item.source().setSourceMediaColourTransform(colorspace) + # add additional metadata from the version to imprint Avalon knob add_keys = [ "frameStart", "frameEnd", "source", "author", @@ -109,9 +112,13 @@ class LoadClip(phiero.SequenceLoader): colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) file = api.get_representation_path(representation).replace("\\", "/") + clip = track_item.source() # reconnect media to new path - track_item.source().reconnectMedia(file) + clip.reconnectMedia(file) + + # set colorspace + clip.setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ @@ -160,6 +167,7 @@ class LoadClip(phiero.SequenceLoader): @classmethod def set_item_color(cls, track_item, version): + clip = track_item.source() # define version name version_name = version.get("name", None) # get all versions in list @@ -172,6 +180,6 @@ class LoadClip(phiero.SequenceLoader): # set clip colour if version_name == max_version: - track_item.source().binItem().setColor(cls.clip_color_last) + clip.binItem().setColor(cls.clip_color_last) else: - track_item.source().binItem().setColor(cls.clip_color) + clip.binItem().setColor(cls.clip_color) From dbb85a40d7eac2ffacf8d49a19bb53a81aef711e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 5 Aug 2021 12:23:33 +0200 Subject: [PATCH 26/47] don't skip instances if is in collecting stage --- openpype/tools/pyblish_pype/control.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py index ae9ca40be5..234135fd9a 100644 --- a/openpype/tools/pyblish_pype/control.py +++ b/openpype/tools/pyblish_pype/control.py @@ -316,6 +316,7 @@ class Controller(QtCore.QObject): self.was_skipped.emit(plugin) continue + in_collect_stage = self.collect_state == 0 if plugin.__instanceEnabled__: instances = pyblish.logic.instances_by_plugin( self.context, plugin @@ -325,7 +326,10 @@ class Controller(QtCore.QObject): continue for instance in instances: - if instance.data.get("publish") is False: + if ( + not in_collect_stage + and instance.data.get("publish") is False + ): pyblish.logic.log.debug( "%s was inactive, skipping.." % instance ) @@ -338,7 +342,7 @@ class Controller(QtCore.QObject): yield (plugin, instance) else: families = util.collect_families_from_instances( - self.context, only_active=True + self.context, only_active=not in_collect_stage ) plugins = pyblish.logic.plugins_by_families( [plugin], families From 1b76d8bf8852b5828114ace04ce187b10ae2602a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 5 Aug 2021 12:23:43 +0200 Subject: [PATCH 27/47] make plugins compatible if were processed --- openpype/tools/pyblish_pype/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py index 50ba27166b..bb1aff2a9a 100644 --- a/openpype/tools/pyblish_pype/model.py +++ b/openpype/tools/pyblish_pype/model.py @@ -498,6 +498,9 @@ class PluginModel(QtGui.QStandardItemModel): ): new_flag_states[PluginStates.HasError] = True + if not publish_states & PluginStates.IsCompatible: + new_flag_states[PluginStates.IsCompatible] = True + item.setData(new_flag_states, Roles.PublishFlagsRole) records = item.data(Roles.LogRecordsRole) or [] From ee3156ce2672e45550309e16c543ce76b8a6eeb6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Aug 2021 10:36:38 +0200 Subject: [PATCH 28/47] #122 - AE local - better logging for thumbnail extraction --- .../plugins/publish/extract_local_render.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index 0f82961bdf..10e5ad5c72 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -1,4 +1,6 @@ import os +import six +import sys import openpype.api from avalon import aftereffects @@ -51,6 +53,7 @@ class ExtractLocalRender(openpype.api.Extractor): # Generate thumbnail. thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg") + args = [ ffmpeg_path, "-y", "-i", first_file_path, @@ -58,7 +61,12 @@ class ExtractLocalRender(openpype.api.Extractor): "-vframes", "1", thumbnail_path ] - output = openpype.lib.run_subprocess(args) + self.log.debug("Thumbnail args:: {}".format(args)) + try: + output = openpype.lib.run_subprocess(args) + except TypeError: + self.log.warning("Error in creating thumbnail") + six.reraise(*sys.exc_info()) instance.data["representations"].append({ "name": "thumbnail", From d2b13996dae5258d552daf9648ae0f48d43344df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Aug 2021 12:00:18 +0200 Subject: [PATCH 29/47] #122 - AE local - better logging for thumbnail extraction --- .../hosts/aftereffects/plugins/publish/extract_local_render.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index 10e5ad5c72..bf2f227d6c 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -17,6 +17,7 @@ class ExtractLocalRender(openpype.api.Extractor): def process(self, instance): stub = aftereffects.stub() staging_dir = instance.data["stagingDir"] + self.log.info("staging_dir::{}".format(staging_dir)) stub.render(staging_dir) @@ -29,6 +30,7 @@ class ExtractLocalRender(openpype.api.Extractor): first_file_path = None files = [] + self.log.info("files::{}".format(os.listdir(staging_dir))) for file_name in os.listdir(staging_dir): files.append(file_name) if first_file_path is None: From 5b7aa3717ff915c25f850e301c5bff2fa4c5eef3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 6 Aug 2021 12:04:35 +0200 Subject: [PATCH 30/47] row order changes are propagated right way with right buttons --- .../settings/settings/list_item_widget.py | 186 ++++++++++-------- 1 file changed, 107 insertions(+), 79 deletions(-) diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index 82ca541132..c9df5caf01 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -117,6 +117,9 @@ class ListItem(QtWidgets.QWidget): self.spacer_widget = spacer_widget + self._row = -1 + self._is_last = False + @property def category_widget(self): return self.entity_widget.category_widget @@ -136,28 +139,40 @@ class ListItem(QtWidgets.QWidget): def add_widget_to_layout(self, widget, label=None): self.content_layout.addWidget(widget, 1) + def set_row(self, row, is_last): + if row == self._row and is_last == self._is_last: + return + + trigger_order_changed = ( + row != self._row + or is_last != self._is_last + ) + self._row = row + self._is_last = is_last + + if trigger_order_changed: + self.order_changed() + + @property def row(self): - return self.entity_widget.input_fields.index(self) + return self._row def parent_rows_count(self): return len(self.entity_widget.input_fields) def _on_add_clicked(self): - self.entity_widget.add_new_item(row=self.row() + 1) + self.entity_widget.add_new_item(row=self.row + 1) def _on_remove_clicked(self): self.entity_widget.remove_row(self) def _on_up_clicked(self): - row = self.row() - self.entity_widget.swap_rows(row - 1, row) + self.entity_widget.swap_rows(self.row - 1, self.row) def _on_down_clicked(self): - row = self.row() - self.entity_widget.swap_rows(row, row + 1) + self.entity_widget.swap_rows(self.row, self.row + 1) def order_changed(self): - row = self.row() parent_row_count = self.parent_rows_count() if parent_row_count == 1: self.up_btn.setVisible(False) @@ -168,11 +183,11 @@ class ListItem(QtWidgets.QWidget): self.up_btn.setVisible(True) self.down_btn.setVisible(True) - if row == 0: + if self.row == 0: self.up_btn.setEnabled(False) self.down_btn.setEnabled(True) - elif row == parent_row_count - 1: + elif self.row == parent_row_count - 1: self.up_btn.setEnabled(True) self.down_btn.setEnabled(False) @@ -191,6 +206,7 @@ class ListWidget(InputWidget): def create_ui(self): self._child_style_state = "" self.input_fields = [] + self._input_fields_by_entity_id = {} main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -243,8 +259,7 @@ class ListWidget(InputWidget): self.entity_widget.add_widget_to_layout(self, entity_label) def set_entity_value(self): - for input_field in tuple(self.input_fields): - self.remove_row(input_field) + self.remove_all_rows() for entity in self.entity.children: self.add_row(entity) @@ -262,39 +277,60 @@ class ListWidget(InputWidget): def _on_entity_change(self): # TODO do less inefficient - input_field_last_idx = len(self.input_fields) - 1 - child_len = len(self.entity) + childen_order = [] + new_children = [] for idx, child_entity in enumerate(self.entity): - if idx > input_field_last_idx: - self.add_row(child_entity, idx) - input_field_last_idx += 1 + input_field = self._input_fields_by_entity_id.get(child_entity.id) + if input_field is not None: + childen_order.append(input_field) + else: + new_children.append((idx, child_entity)) + + order_changed = False + for idx, input_field in enumerate(childen_order): + current_field = self.input_fields[idx] + if current_field is input_field: continue + order_changed = True + old_idx = self.input_fields.index(input_field) + self.input_fields[old_idx], self.input_fields[idx] = ( + current_field, input_field + ) + self.content_layout.insertWidget(idx + 1, input_field) - if self.input_fields[idx].entity is child_entity: - continue + kept_len = len(childen_order) + fields_len = len(self.input_fields) + if fields_len > kept_len: + order_changed = True + for row in reversed(range(kept_len, fields_len)): + self.remove_row(row=row) - input_field_idx = None - for _input_field_idx, input_field in enumerate(self.input_fields): - if input_field.entity is child_entity: - input_field_idx = _input_field_idx - break + for idx, child_entity in new_children: + order_changed = False + self.add_row(child_entity, idx) - if input_field_idx is None: - self.add_row(child_entity, idx) - input_field_last_idx += 1 - continue + if not order_changed: + return - input_field = self.input_fields.pop(input_field_idx) - self.input_fields.insert(idx, input_field) - self.content_layout.insertWidget(idx, input_field) + self._on_order_change() - new_input_field_len = len(self.input_fields) - if child_len != new_input_field_len: - for _idx in range(child_len, new_input_field_len): - # Remove row at the same index - self.remove_row(self.input_fields[child_len]) + input_field_len = self.count() + self.empty_row.setVisible(input_field_len == 0) - self.empty_row.setVisible(self.count() == 0) + def _on_order_change(self): + last_idx = self.count() - 1 + previous_input = None + for idx, input_field in enumerate(self.input_fields): + input_field.set_row(idx, idx == last_idx) + next_input = input_field.input_field.focusProxy() + if previous_input is not None: + self.setTabOrder(previous_input, next_input) + else: + self.setTabOrder(self, next_input) + previous_input = next_input + + if previous_input is not None: + self.setTabOrder(previous_input, self) def count(self): return len(self.input_fields) @@ -307,32 +343,20 @@ class ListWidget(InputWidget): def add_new_item(self, row=None): new_entity = self.entity.add_new_item(row) - for input_field in self.input_fields: - if input_field.entity is new_entity: - input_field.input_field.setFocus(True) - break + input_field = self._input_fields_by_entity_id.get(new_entity.id) + if input_field is not None: + input_field.input_field.setFocus(True) return new_entity def add_row(self, child_entity, row=None): # Create new item item_widget = ListItem(child_entity, self) - - previous_field = None - next_field = None + self._input_fields_by_entity_id[child_entity.id] = item_widget if row is None: - if self.input_fields: - previous_field = self.input_fields[-1] self.content_layout.addWidget(item_widget) self.input_fields.append(item_widget) else: - if row > 0: - previous_field = self.input_fields[row - 1] - - max_index = self.count() - if row < max_index: - next_field = self.input_fields[row] - self.content_layout.insertWidget(row + 1, item_widget) self.input_fields.insert(row, item_widget) @@ -342,49 +366,53 @@ class ListWidget(InputWidget): # added as widget here which won't because is not in input_fields item_widget.input_field.set_entity_value() - if previous_field: - previous_field.order_changed() + self._on_order_change() - if next_field: - next_field.order_changed() - - item_widget.order_changed() - - previous_input = None - for input_field in self.input_fields: - if previous_input is not None: - self.setTabOrder( - previous_input, input_field.input_field.focusProxy() - ) - previous_input = input_field.input_field.focusProxy() + input_field_len = self.count() + self.empty_row.setVisible(input_field_len == 0) self.updateGeometry() - def remove_row(self, item_widget): - row = self.input_fields.index(item_widget) - previous_field = None - next_field = None - if row > 0: - previous_field = self.input_fields[row - 1] + def remove_all_rows(self): + self._input_fields_by_entity_id = {} + while self.input_fields: + item_widget = self.input_fields.pop(0) + self.content_layout.removeWidget(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() - if row != len(self.input_fields) - 1: - next_field = self.input_fields[row + 1] + self.empty_row.setVisible(True) + + self.updateGeometry() + + def remove_row(self, item_widget=None, row=None): + if item_widget is None: + item_widget = self.input_fields[row] + elif row is None: + row = self.input_fields.index(item_widget) self.content_layout.removeWidget(item_widget) self.input_fields.pop(row) + self._input_fields_by_entity_id.pop(item_widget.entity.id) item_widget.setParent(None) item_widget.deleteLater() if item_widget.entity in self.entity: self.entity.remove(item_widget.entity) - if previous_field: - previous_field.order_changed() + rows = self.count() + any_item = rows == 0 + if any_item: + start_row = 0 + if row > 0: + start_row = row - 1 - if next_field: - next_field.order_changed() + last_row = rows - 1 + _enum = enumerate(self.input_fields[start_row:rows]) + for idx, _item_widget in _enum: + _item_widget.set_row(idx, idx == last_row) - self.empty_row.setVisible(self.count() == 0) + self.empty_row.setVisible(any_item) self.updateGeometry() From abdfe4ee38de2589400aa030e52fb11c795b749d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Aug 2021 12:47:39 +0200 Subject: [PATCH 31/47] AE local render - fixed single render file --- .../aftereffects/plugins/publish/extract_local_render.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index bf2f227d6c..a3858112c3 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -37,12 +37,16 @@ class ExtractLocalRender(openpype.api.Extractor): first_file_path = os.path.join(staging_dir, file_name) + resulting_files = files + if len(files) == 1: + resulting_files = files[0] + repre_data = { "frameStart": instance.data["frameStart"], "frameEnd": instance.data["frameEnd"], "name": ext, "ext": ext, - "files": files, + "files": resulting_files, "stagingDir": staging_dir } if instance.data["review"]: From 66dc345e681b4156e80c0e9374794144952fe081 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Aug 2021 14:47:11 +0200 Subject: [PATCH 32/47] AE local render - add create review and burnin --- openpype/plugins/publish/extract_burnin.py | 3 ++- openpype/plugins/publish/extract_review.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index ef52d51325..91e0a0f3ec 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -44,7 +44,8 @@ class ExtractBurnin(openpype.api.Extractor): "harmony", "fusion", "aftereffects", - "tvpaint" + "tvpaint", + "aftereffects" # "resolve" ] optional = True diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index de54b554e3..bdcd3b8e60 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -44,7 +44,8 @@ class ExtractReview(pyblish.api.InstancePlugin): "standalonepublisher", "fusion", "tvpaint", - "resolve" + "resolve", + "aftereffects" ] # Supported extensions From 14bfd47b6925a630d75aaeff394e80a2154f027c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Aug 2021 16:42:19 +0200 Subject: [PATCH 33/47] AE local render - fix - ftrackreview shouldn't be on sequence representation --- .../hosts/aftereffects/plugins/publish/extract_local_render.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py index a3858112c3..37337e7fee 100644 --- a/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_local_render.py @@ -50,8 +50,7 @@ class ExtractLocalRender(openpype.api.Extractor): "stagingDir": staging_dir } if instance.data["review"]: - repre_data["preview"] = True, - repre_data["tags"] = ["review", "ftrackreview"] + repre_data["tags"] = ["review"] instance.data["representations"] = [repre_data] From 9e8e8ec656de4442a83a8753a9e8a43610bc3af5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 Aug 2021 18:13:25 +0200 Subject: [PATCH 34/47] hiero: fix colorspace attribute distribution --- openpype/hosts/hiero/plugins/load/load_clip.py | 6 ++++-- .../hiero/plugins/publish/precollect_instances.py | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index fa666207c5..b905dd4431 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -55,7 +55,8 @@ class LoadClip(phiero.SequenceLoader): **context["representation"]["context"]) # set colorspace - track_item.source().setSourceMediaColourTransform(colorspace) + if colorspace: + track_item.source().setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ @@ -118,7 +119,8 @@ class LoadClip(phiero.SequenceLoader): clip.reconnectMedia(file) # set colorspace - clip.setSourceMediaColourTransform(colorspace) + if colorspace: + clip.setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 4984849aa7..9b529edf88 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -120,6 +120,13 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # create instance instance = context.create_instance(**data) + # add colorspace data + instance.data.update({ + "versionData": { + "colorspace": track_item.sourceMediaColourTransform(), + } + }) + # create shot instance for shot attributes create/update self.create_shot_instance(context, **data) @@ -133,13 +140,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # create audio subset instance self.create_audio_instance(context, **data) - # add colorspace data - instance.data.update({ - "versionData": { - "colorspace": track_item.sourceMediaColourTransform(), - } - }) - # add audioReview attribute to plate instance data # if reviewTrack is on if tag_data.get("reviewTrack") is not None: From 68baae0b68223011ad2a7dab5d19705b0674e60e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 7 Aug 2021 03:40:49 +0000 Subject: [PATCH 35/47] [Automated] Bump version --- CHANGELOG.md | 17 ++++++++++------- openpype/version.py | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e3f2150c8..964120330e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ # Changelog -## [3.3.0-nightly.7](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.3.0-nightly.8](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.2.0...HEAD) **🚀 Enhancements** +- Feature AE local render [\#1901](https://github.com/pypeclub/OpenPype/pull/1901) +- Ftrack: Where I run action enhancement [\#1900](https://github.com/pypeclub/OpenPype/pull/1900) +- Ftrack: Private project server actions [\#1899](https://github.com/pypeclub/OpenPype/pull/1899) +- Support nested studio plugins paths. [\#1898](https://github.com/pypeclub/OpenPype/pull/1898) - Settings: global validators with options [\#1892](https://github.com/pypeclub/OpenPype/pull/1892) - Settings: Conditional dict enum positioning [\#1891](https://github.com/pypeclub/OpenPype/pull/1891) - Expose stop timer through rest api. [\#1886](https://github.com/pypeclub/OpenPype/pull/1886) @@ -25,11 +29,12 @@ - nuke: settings create missing default subsets [\#1829](https://github.com/pypeclub/OpenPype/pull/1829) - Update poetry lock [\#1823](https://github.com/pypeclub/OpenPype/pull/1823) - Settings: settings for plugins [\#1819](https://github.com/pypeclub/OpenPype/pull/1819) -- Maya: Deadline custom settings [\#1797](https://github.com/pypeclub/OpenPype/pull/1797) - Maya: Shader name validation [\#1762](https://github.com/pypeclub/OpenPype/pull/1762) **🐛 Bug fixes** +- Pyblish UI: Fix collecting stage processing [\#1903](https://github.com/pypeclub/OpenPype/pull/1903) +- Burnins: Use input's bitrate in h624 [\#1902](https://github.com/pypeclub/OpenPype/pull/1902) - Bug: fixed python detection [\#1893](https://github.com/pypeclub/OpenPype/pull/1893) - global: integrate name missing default template [\#1890](https://github.com/pypeclub/OpenPype/pull/1890) - publisher: editorial plugins fixes [\#1889](https://github.com/pypeclub/OpenPype/pull/1889) @@ -44,6 +49,7 @@ - Maya: fix yeti settings path in extractor [\#1809](https://github.com/pypeclub/OpenPype/pull/1809) - Failsafe for cross project containers. [\#1806](https://github.com/pypeclub/OpenPype/pull/1806) - Houdini colector formatting keys fix [\#1802](https://github.com/pypeclub/OpenPype/pull/1802) +- Settings error dialog on show [\#1798](https://github.com/pypeclub/OpenPype/pull/1798) **Merged pull requests:** @@ -59,6 +65,7 @@ - Nuke: ftrack family plugin settings preset [\#1805](https://github.com/pypeclub/OpenPype/pull/1805) - Standalone publisher last project [\#1799](https://github.com/pypeclub/OpenPype/pull/1799) +- Maya: Deadline custom settings [\#1797](https://github.com/pypeclub/OpenPype/pull/1797) - Ftrack Multiple notes as server action [\#1795](https://github.com/pypeclub/OpenPype/pull/1795) - Settings conditional dict [\#1777](https://github.com/pypeclub/OpenPype/pull/1777) - Settings application use python 2 only where needed [\#1776](https://github.com/pypeclub/OpenPype/pull/1776) @@ -68,7 +75,6 @@ - Application executables with environment variables [\#1757](https://github.com/pypeclub/OpenPype/pull/1757) - Deadline: Nuke submission additional attributes [\#1756](https://github.com/pypeclub/OpenPype/pull/1756) - Settings schema without prefill [\#1753](https://github.com/pypeclub/OpenPype/pull/1753) -- Settings Hosts enum [\#1739](https://github.com/pypeclub/OpenPype/pull/1739) **🐛 Bug fixes** @@ -76,6 +82,7 @@ - Collect ftrack family bugs [\#1801](https://github.com/pypeclub/OpenPype/pull/1801) - Invitee email can be None which break the Ftrack commit. [\#1788](https://github.com/pypeclub/OpenPype/pull/1788) - Fix: staging and `--use-version` option [\#1786](https://github.com/pypeclub/OpenPype/pull/1786) +- Otio unrelated error on import [\#1782](https://github.com/pypeclub/OpenPype/pull/1782) - FFprobe streams order [\#1775](https://github.com/pypeclub/OpenPype/pull/1775) - Fix - single file files are str only, cast it to list to count properly [\#1772](https://github.com/pypeclub/OpenPype/pull/1772) - Environments in app executable for MacOS [\#1768](https://github.com/pypeclub/OpenPype/pull/1768) @@ -84,10 +91,6 @@ - Standalone publisher thumbnail extractor fix [\#1761](https://github.com/pypeclub/OpenPype/pull/1761) - Anatomy others templates don't cause crash [\#1758](https://github.com/pypeclub/OpenPype/pull/1758) - Backend acre module commit update [\#1745](https://github.com/pypeclub/OpenPype/pull/1745) -- hiero: precollect instances failing when audio selected [\#1743](https://github.com/pypeclub/OpenPype/pull/1743) -- Hiero: creator instance error [\#1742](https://github.com/pypeclub/OpenPype/pull/1742) -- Nuke: fixing render creator for no selection format failing [\#1741](https://github.com/pypeclub/OpenPype/pull/1741) -- StandalonePublisher: failing collector for editorial [\#1738](https://github.com/pypeclub/OpenPype/pull/1738) **Merged pull requests:** diff --git a/openpype/version.py b/openpype/version.py index 473be3bafc..c888e5f9d9 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.3.0-nightly.7" +__version__ = "3.3.0-nightly.8" From 7b71e1237a13e49b2da86ba7c664dd038244a458 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 9 Aug 2021 12:22:01 +0200 Subject: [PATCH 36/47] submodules: avalon-core update --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index cfd4191e36..e5c8a15fde 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit cfd4191e364b47de7364096f45d9d9d9a901692a +Subproject commit e5c8a15fde77708c924eab3018bda255f17b5390 From 5b71c522a50336d7777c7785e09d9e337f5a9503 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 12:48:06 +0200 Subject: [PATCH 37/47] added missing function to init file --- openpype/modules/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/modules/__init__.py b/openpype/modules/__init__.py index 81853faa38..583480b049 100644 --- a/openpype/modules/__init__.py +++ b/openpype/modules/__init__.py @@ -2,6 +2,9 @@ from .base import ( OpenPypeModule, OpenPypeInterface, + + load_modules, + ModulesManager, TrayModulesManager ) @@ -11,6 +14,8 @@ __all__ = ( "OpenPypeModule", "OpenPypeInterface", + "load_modules", + "ModulesManager", "TrayModulesManager" ) From b6383ccb9afec966edd533e293f050160e23a9db Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 12:50:16 +0200 Subject: [PATCH 38/47] fixed conflict changes --- .../ftrack/event_handlers_user/action_where_run_ask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py b/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py index 2c427cfff7..b4133fbe78 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py @@ -2,7 +2,7 @@ import platform import socket import getpass -from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype_modules.ftrack.lib import BaseAction, statics_icon class ActionWhereIRun(BaseAction): From a44805ae36a378c11e7861f90b891914d91149b6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Aug 2021 12:52:15 +0200 Subject: [PATCH 39/47] removed unused import --- .../ftrack/event_handlers_user/action_where_run_ask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py b/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py index b4133fbe78..0d69913996 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_user/action_where_run_ask.py @@ -2,7 +2,7 @@ import platform import socket import getpass -from openpype_modules.ftrack.lib import BaseAction, statics_icon +from openpype_modules.ftrack.lib import BaseAction class ActionWhereIRun(BaseAction): From a4f9ee1496f34f94d5e476b6614ecb772784fd93 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 9 Aug 2021 15:24:56 +0200 Subject: [PATCH 40/47] Fix - texture validators for workfiles triggers only for textures workfiles It was triggering for all workfiles --- .../standalonepublisher/plugins/publish/collect_texture.py | 1 + .../plugins/publish/validate_texture_batch.py | 2 +- .../plugins/publish/validate_texture_name.py | 2 +- .../plugins/publish/validate_texture_workfiles.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py index d70a0a75b8..596a8ccfd2 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py @@ -270,6 +270,7 @@ class CollectTextures(pyblish.api.ContextPlugin): # store origin if family == 'workfile': families = self.workfile_families + families.append("texture_batch_workfile") new_instance.data["source"] = "standalone publisher" else: diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py index af200b59e0..d592a4a059 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py @@ -8,7 +8,7 @@ class ValidateTextureBatch(pyblish.api.InstancePlugin): label = "Validate Texture Presence" hosts = ["standalonepublisher"] order = openpype.api.ValidateContentsOrder - families = ["workfile"] + families = ["texture_batch_workfile"] optional = False def process(self, instance): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py index 92f930c3fc..f210be3631 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py @@ -8,7 +8,7 @@ class ValidateTextureBatchNaming(pyblish.api.InstancePlugin): label = "Validate Texture Batch Naming" hosts = ["standalonepublisher"] order = openpype.api.ValidateContentsOrder - families = ["workfile", "textures"] + families = ["texture_batch_workfile", "textures"] optional = False def process(self, instance): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py index aa3aad71db..25bb5aea4a 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py @@ -11,7 +11,7 @@ class ValidateTextureBatchWorkfiles(pyblish.api.InstancePlugin): label = "Validate Texture Workfile Has Resources" hosts = ["standalonepublisher"] order = openpype.api.ValidateContentsOrder - families = ["workfile"] + families = ["texture_batch_workfile"] optional = True # from presets From c386fc340dcfe02f5a0d6c440b9ab8040052b51f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 Aug 2021 12:15:31 +0200 Subject: [PATCH 41/47] Nuke: update video file crassing --- openpype/hosts/nuke/plugins/load/load_mov.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py index d84c3d4c71..95f20b305f 100644 --- a/openpype/hosts/nuke/plugins/load/load_mov.py +++ b/openpype/hosts/nuke/plugins/load/load_mov.py @@ -259,7 +259,7 @@ class LoadMov(api.Loader): read_node["last"].setValue(last) read_node['frame_mode'].setValue("start at") - if int(self.first_frame) == int(read_node['frame'].value()): + if int(float(self.first_frame)) == int(float(read_node['frame'].value())): # start at workfile start read_node['frame'].setValue(str(self.first_frame)) else: From 3be9fa8cf170184c9ed0003a024d503fb77956bc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 Aug 2021 12:20:06 +0200 Subject: [PATCH 42/47] hound: pep8 --- openpype/hosts/nuke/plugins/load/load_mov.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py index 95f20b305f..f7523d0a6e 100644 --- a/openpype/hosts/nuke/plugins/load/load_mov.py +++ b/openpype/hosts/nuke/plugins/load/load_mov.py @@ -259,7 +259,8 @@ class LoadMov(api.Loader): read_node["last"].setValue(last) read_node['frame_mode'].setValue("start at") - if int(float(self.first_frame)) == int(float(read_node['frame'].value())): + if int(float(self.first_frame)) == int( + float(read_node['frame'].value())): # start at workfile start read_node['frame'].setValue(str(self.first_frame)) else: From 1e50751d9def8c3db79bf3d900dfec0f5da21755 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 10 Aug 2021 14:51:22 +0200 Subject: [PATCH 43/47] Changed missed import --- openpype/tools/tray_app/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/tray_app/app.py b/openpype/tools/tray_app/app.py index 339e6343f8..03f8321464 100644 --- a/openpype/tools/tray_app/app.py +++ b/openpype/tools/tray_app/app.py @@ -9,7 +9,7 @@ import itertools from datetime import datetime from avalon import style -from openpype.modules.webserver import host_console_listener +from openpype_modules.webserver import host_console_listener from Qt import QtWidgets, QtCore From 976bc45a1e595273024a3711a6c3269f162247e8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 10 Aug 2021 15:18:53 +0200 Subject: [PATCH 44/47] Changed missed imports --- .../clockify/ftrack/server/action_clockify_sync_server.py | 2 +- .../clockify/ftrack/user/action_clockify_sync_local.py | 2 +- .../default_modules/clockify/launcher_actions/ClockifyStart.py | 2 +- .../default_modules/clockify/launcher_actions/ClockifySync.py | 2 +- .../default_modules/slack/launch_hooks/pre_python2_vendor.py | 2 +- .../event_handlers_server/action_private_project_detection.py | 2 +- openpype/pype_commands.py | 2 +- openpype/settings/entities/enum_entity.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/modules/default_modules/clockify/ftrack/server/action_clockify_sync_server.py b/openpype/modules/default_modules/clockify/ftrack/server/action_clockify_sync_server.py index 8379414c0c..c6b55947da 100644 --- a/openpype/modules/default_modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/openpype/modules/default_modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,7 +1,7 @@ import os import json from openpype_modules.ftrack.lib import ServerAction -from openpype.modules.clockify.clockify_api import ClockifyAPI +from openpype_modules.clockify.clockify_api import ClockifyAPI class SyncClocifyServer(ServerAction): diff --git a/openpype/modules/default_modules/clockify/ftrack/user/action_clockify_sync_local.py b/openpype/modules/default_modules/clockify/ftrack/user/action_clockify_sync_local.py index 3d55ee92b6..a430791906 100644 --- a/openpype/modules/default_modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/openpype/modules/default_modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -1,6 +1,6 @@ import json from openpype_modules.ftrack.lib import BaseAction, statics_icon -from openpype.modules.clockify.clockify_api import ClockifyAPI +from openpype_modules.clockify.clockify_api import ClockifyAPI class SyncClocifyLocal(BaseAction): diff --git a/openpype/modules/default_modules/clockify/launcher_actions/ClockifyStart.py b/openpype/modules/default_modules/clockify/launcher_actions/ClockifyStart.py index c431ea240d..db51964eb7 100644 --- a/openpype/modules/default_modules/clockify/launcher_actions/ClockifyStart.py +++ b/openpype/modules/default_modules/clockify/launcher_actions/ClockifyStart.py @@ -1,6 +1,6 @@ from avalon import api, io from openpype.api import Logger -from openpype.modules.clockify.clockify_api import ClockifyAPI +from openpype_modules.clockify.clockify_api import ClockifyAPI log = Logger().get_logger(__name__) diff --git a/openpype/modules/default_modules/clockify/launcher_actions/ClockifySync.py b/openpype/modules/default_modules/clockify/launcher_actions/ClockifySync.py index 1bb168a80b..02982d373a 100644 --- a/openpype/modules/default_modules/clockify/launcher_actions/ClockifySync.py +++ b/openpype/modules/default_modules/clockify/launcher_actions/ClockifySync.py @@ -1,5 +1,5 @@ from avalon import api, io -from openpype.modules.clockify.clockify_api import ClockifyAPI +from openpype_modules.clockify.clockify_api import ClockifyAPI from openpype.api import Logger log = Logger().get_logger(__name__) diff --git a/openpype/modules/default_modules/slack/launch_hooks/pre_python2_vendor.py b/openpype/modules/default_modules/slack/launch_hooks/pre_python2_vendor.py index a2c1f8a9e0..0f4bc22a34 100644 --- a/openpype/modules/default_modules/slack/launch_hooks/pre_python2_vendor.py +++ b/openpype/modules/default_modules/slack/launch_hooks/pre_python2_vendor.py @@ -1,6 +1,6 @@ import os from openpype.lib import PreLaunchHook -from openpype.modules.slack import SLACK_MODULE_DIR +from openpype_modules.slack import SLACK_MODULE_DIR class PrePython2Support(PreLaunchHook): diff --git a/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py b/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py index 5213e10ba3..62772740cd 100644 --- a/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py +++ b/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py @@ -1,4 +1,4 @@ -from openpype.modules.ftrack.lib import ServerAction +from openpype_modules.ftrack.lib import ServerAction class PrivateProjectDetectionAction(ServerAction): diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 7c47d8c613..978dcbc0d7 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -35,7 +35,7 @@ class PypeCommands: @staticmethod def launch_eventservercli(*args): - from openpype.modules.ftrack.ftrack_server.event_server_cli import ( + from openpype_modules.ftrack.ftrack_server.event_server_cli import ( run_event_server ) return run_event_server(*args) diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 4f6a2886bc..31ce96a059 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -399,7 +399,7 @@ class ProvidersEnum(BaseEnumEntity): self.placeholder = None def _get_enum_values(self): - from openpype.modules.sync_server.providers import lib as lib_providers + from openpype_modules.sync_server.providers import lib as lib_providers providers = lib_providers.factory.providers From 3d4c18941a77efa6c78f1f69aa8b594f3c047e09 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 15:25:45 +0200 Subject: [PATCH 45/47] modified imports in comments --- .../ftrack/event_handlers_user/action_create_cust_attrs.py | 2 +- .../ftrack/plugins/publish/integrate_hierarchy_ftrack.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 599d2eb257..3869d8ad08 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -43,7 +43,7 @@ dictionary level, task's attributes are nested more. group (string) - name of group - - based on attribute `openpype.modules.ftrack.lib.CUST_ATTR_GROUP` + - based on attribute `openpype_modules.ftrack.lib.CUST_ATTR_GROUP` - "pype" by default *** Required *************************************************************** diff --git a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 2fd5296d24..fbd64d9f70 100644 --- a/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/default_modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -4,7 +4,7 @@ import six import pyblish.api from avalon import io -# Copy of constant `openpype.modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` +# Copy of constant `openpype_modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" CUST_ATTR_GROUP = "openpype" From 2bbb5e0fc10e67b0b3325bcd915a717249237f41 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 15:40:11 +0200 Subject: [PATCH 46/47] added a little bit readme info --- openpype/modules/README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/modules/README.md b/openpype/modules/README.md index d54ba7c835..a3733518ac 100644 --- a/openpype/modules/README.md +++ b/openpype/modules/README.md @@ -1,5 +1,17 @@ -# OpenPype modules -OpenPype modules should contain separated logic of specific kind of implementation, like Ftrack connection and usage code or Deadline farm rendering or special plugins. +# OpenPype modules/addons +OpenPype modules should contain separated logic of specific kind of implementation, like Ftrack connection and usage code or Deadline farm rendering or may contain only special plugins. Addons work the same way currently there is no difference in module and addon. + +## Modules concept +- modules and addons are dynamically imported to virtual python module `openpype_modules` from which it is possible to import them no matter where is the modulo located +- modules or addons should never be imported directly even if you know possible full import path + - it is because all of their content must be imported in specific order and should not be imported without defined functions as it may also break few implementation parts + +### TODOs +- add module/addon manifest + - definition of module (not 100% defined content e.g. minimum require OpenPype version etc.) + - defying that folder is content of a module or an addon +- module/addon have it's settings schemas and default values outside OpenPype +- add general setting of paths to modules ## Base class `OpenPypeModule` - abstract class as base for each module @@ -20,6 +32,7 @@ OpenPype modules should contain separated logic of specific kind of implementati - interfaces can be defined in `interfaces.py` inside module directory - the file can't use relative imports or import anything from other parts of module itself at the header of file + - this is one of reasons why modules/addons can't be imported directly without using defined functions in OpenPype modules implementation ## Base class `OpenPypeInterface` - has nothing implemented From abd7bfa375df0fdea3241b0019c871627a72c803 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 10 Aug 2021 15:41:03 +0200 Subject: [PATCH 47/47] moved new file to right folder --- .../event_handlers_server/action_private_project_detection.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/modules/{ => default_modules}/ftrack/event_handlers_server/action_private_project_detection.py (100%) diff --git a/openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py b/openpype/modules/default_modules/ftrack/event_handlers_server/action_private_project_detection.py similarity index 100% rename from openpype/modules/ftrack/event_handlers_server/action_private_project_detection.py rename to openpype/modules/default_modules/ftrack/event_handlers_server/action_private_project_detection.py