From 021454dc9bc72e9d09c0e4447f3d37647ad79669 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 11 Mar 2023 06:59:43 +0100 Subject: [PATCH 01/18] Fix #4044: Don't rely on containers, but just get ids from the nodes --- openpype/tools/mayalookassigner/commands.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index 2e7a51efde..6955dda97f 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -80,21 +80,9 @@ def get_all_asset_nodes(): Returns: list: list of dictionaries """ - - host = registered_host() - - nodes = [] - for container in host.ls(): - # We are not interested in looks but assets! - if container["loader"] == "LookLoader": - continue - - # Gather all information - container_name = container["objectName"] - nodes += lib.get_container_members(container_name) - - nodes = list(set(nodes)) - return nodes + nodes = cmds.ls(dag=True, noIntermediate=True, long=True) + items = commands.create_items_from_nodes(nodes) + return items def create_asset_id_hash(nodes): From b44b0b8fb063aeef51761a2b179cd9e074a9a5f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 11 Mar 2023 07:57:20 +0100 Subject: [PATCH 02/18] Remove `commands.` prefix since function is local --- openpype/tools/mayalookassigner/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index 6955dda97f..0135eb4f49 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -81,7 +81,7 @@ def get_all_asset_nodes(): list: list of dictionaries """ nodes = cmds.ls(dag=True, noIntermediate=True, long=True) - items = commands.create_items_from_nodes(nodes) + items = create_items_from_nodes(nodes) return items From be463b05dc2fae9eda44bd2ce3bd1fd47eca82d0 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 18 Jan 2023 15:55:40 +0000 Subject: [PATCH 03/18] documentation: Add `ARCHITECTURE.md` document This document attemps to give a quick overview of the project to help onboarding, it's not an extensive documentation but more of a elevator pitch one-line descriptions of files/directories and what the attempt to do. --- ARCHITECTURE.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000000..912780d803 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,77 @@ +# Architecture + +OpenPype is a monolithic Python project that bundles several parts, this document will try to give a birds eye overview of the project and, to a certain degree, each of the sub-projects. +The current file structure looks like this: + +``` +. +├── common - Code in this folder is backend portion of Addon distribution logic for v4 server. +├── docs - Documentation of the source code. +├── igniter - The OpenPype bootstrapper, deals with running version resolution and setting up the connection to the mongodb. +├── openpype - The actual OpenPype core package. +├── schema - Collection of JSON files describing schematics of objects. This follows Avalon's convention. +├── tests - Integration and unit tests. +├── tools - Conveninece scripts to perform common actions (in both bash and ps1). +├── vendor - When using the igniter, it deploys third party tools in here, such as ffmpeg. +└── website - Source files for https://openpype.io/ which is Docusaursus (https://docusaurus.io/). +``` + +The core functionality of the pipeline can be found in `igniter` and `openpype`, which in turn rely on the `schema` files, whenever you build (or download a pre-built) version of OpenPype, these two are bundled in there, and `Igniter` is the entry point. + + +## Igniter + +It's the setup and update tool for OpenPype, unless you want to package `openpype` separately and deal with all the config manually, this will most likely be your entry point. + +``` +igniter/ +├── bootstrap_repos.py - Module that will find or install OpenPype versions in the system. +├── __init__.py - Igniter entry point. +├── install_dialog.py- Show dialog for choosing central pype repository. +├── install_thread.py - Threading helpers for the install process. +├── __main__.py - Like `__init__.py` ? +├── message_dialog.py - Qt Dialog with a message and "Ok" button. +├── nice_progress_bar.py - Fancy Qt progress bar. +├── splash.txt - ASCII art for the terminal installer. +├── stylesheet.css - Installer Qt styles. +├── terminal_splash.py - Terminal installer animation, relies in `splash.txt`. +├── tools.py - Collection of methods that don't fit in other modules. +├── update_thread.py - Threading helper to update existing OpenPype installs. +├── update_window.py - Qt UI to update OpenPype installs. +├── user_settings.py - Interface for the OpenPype user settings. +└── version.py - Igniter's version number. +``` + +## OpenPype + +This is the main package of the OpenPype logic, it could be loosely described as a combination of [Avalon](https://getavalon.github.io), [Pyblish](https://pyblish.com/) and glue around those with custom OpenPype only elements, things are in progress of being moved around to better prepare for V4, which will be released under a new name AYON. + +``` +openpype/ +├── client - Interface for the MongoDB. +├── hooks - Hooks to be executed on certain OpenPype Applications defined in `openpype.lib.applications`. +├── host - Base class for the different hosts. +├── hosts - Integration with the different DCCs (hosts) using the `host` base class. +├── lib - Libraries that stitch together the package, some have been moved into other parts. +├── modules - OpenPype modules should contain separated logic of specific kind of implementation, such as Ftrack connection and its python API. +├── pipeline - Core of the OpenPype pipeline, handles creation of data, publishing, etc. +├── plugins - Global/core plugins for loader and publisher tool. +├── resources - Icons, fonts, etc. +├── scripts - Loose scipts that get run by tools/publishers. +├── settings - OpenPype settings interface. +├── style - Qt styling. +├── tests - Unit tests. +├── tools - Core tools, check out https://openpype.io/docs/artist_tools. +├── vendor - Vendoring of needed required Python packes. +├── widgets - Common re-usable Qt Widgets. +├── action.py - LEGACY: Lives now in `openpype.pipeline.publish.action` Pyblish actions. +├── cli.py - Command line interface, leverages `click`. +├── __init__.py - Sets two constants. +├── __main__.py - Entry point, calls the `cli.py` +├── plugin.py - Pyblish plugins. +├── pype_commands.py - Implementation of OpenPype commands. +└── version.py - Current version number. +``` + + + From f1c490b21cf69294b7c1ab7e03a8115557c552de Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Mar 2023 10:57:16 +0100 Subject: [PATCH 04/18] Fix `get_all_asset_nodes` --- openpype/hosts/maya/tools/mayalookassigner/commands.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/maya/tools/mayalookassigner/commands.py b/openpype/hosts/maya/tools/mayalookassigner/commands.py index 0135eb4f49..3d9746511d 100644 --- a/openpype/hosts/maya/tools/mayalookassigner/commands.py +++ b/openpype/hosts/maya/tools/mayalookassigner/commands.py @@ -80,9 +80,7 @@ def get_all_asset_nodes(): Returns: list: list of dictionaries """ - nodes = cmds.ls(dag=True, noIntermediate=True, long=True) - items = create_items_from_nodes(nodes) - return items + return cmds.ls(dag=True, noIntermediate=True, long=True) def create_asset_id_hash(nodes): From f823b065d312c8f9dc24d10d51ebcb014319866c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Mar 2023 22:36:11 +0100 Subject: [PATCH 05/18] celaction: moving conditional workfile parameters into settings --- .../celaction/hooks/pre_celaction_setup.py | 22 +++++++++++-------- .../publish/collect_celaction_cli_kwargs.py | 2 +- .../defaults/project_settings/celaction.json | 9 ++++++++ .../schema_project_celaction.json | 14 ++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/celaction/hooks/pre_celaction_setup.py b/openpype/hosts/celaction/hooks/pre_celaction_setup.py index 62cebf99ed..7506f1a52c 100644 --- a/openpype/hosts/celaction/hooks/pre_celaction_setup.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_setup.py @@ -38,8 +38,9 @@ class CelactionPrelaunchHook(PreLaunchHook): ) path_to_cli = os.path.join(CELACTION_SCRIPTS_DIR, "publish_cli.py") - subproces_args = get_openpype_execute_args("run", path_to_cli) - openpype_executable = subproces_args.pop(0) + subprocess_args = get_openpype_execute_args("run", path_to_cli) + openpype_executable = subprocess_args.pop(0) + workfile_settings = self.get_workfile_settings() winreg.SetValueEx( hKey, @@ -49,15 +50,15 @@ class CelactionPrelaunchHook(PreLaunchHook): openpype_executable ) - parameters = subproces_args + [ - "--currentFile", "*SCENE*", - "--chunk", "*CHUNK*", - "--frameStart", "*START*", - "--frameEnd", "*END*", - "--resolutionWidth", "*X*", - "--resolutionHeight", "*Y*" + # add required arguments for workfile path + parameters = subprocess_args + [ + "--currentFile", "*SCENE*" ] + # Add custom parameters from workfile settings + if workfile_settings["parameters"]: + parameters += workfile_settings["parameters"] + winreg.SetValueEx( hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, subprocess.list2cmdline(parameters) @@ -135,3 +136,6 @@ class CelactionPrelaunchHook(PreLaunchHook): self.log.info(f"Workfile to open: \"{workfile_path}\"") return workfile_path + + def get_workfile_settings(self): + return self.data["project_settings"]["celaction"]["workfile"] diff --git a/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py index 43b81b83e7..54dea15dff 100644 --- a/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py +++ b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py @@ -39,7 +39,7 @@ class CollectCelactionCliKwargs(pyblish.api.Collector): passing_kwargs[key] = value if missing_kwargs: - raise RuntimeError("Missing arguments {}".format( + self.log.debug("Missing arguments {}".format( ", ".join( [f'"{key}"' for key in missing_kwargs] ) diff --git a/openpype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json index bdba6d7322..0194b2c48c 100644 --- a/openpype/settings/defaults/project_settings/celaction.json +++ b/openpype/settings/defaults/project_settings/celaction.json @@ -9,6 +9,15 @@ "rules": {} } }, + "workfile": { + "parameters": [ + "--chunk *CHUNK*", + "--frameStart *START*", + "--frameEnd *END*", + "--resolutionWidth *X*", + "--resolutionHeight *Y*" + ] + }, "publish": { "CollectRenderPath": { "output_extension": "png", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index 2320d9ae26..4ca3cbb4da 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -22,6 +22,20 @@ ] }, + { + "type": "dict", + "collapsible": true, + "key": "workfile", + "label": "Workfile", + "children": [ + { + "key": "parameters", + "label": "Parameters", + "type": "list", + "object_type": "text" + } + ] + }, { "type": "dict", "collapsible": true, From 230139baf6dfdad8b131b2547ce76d474f11f777 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 22 Mar 2023 23:32:19 +0100 Subject: [PATCH 06/18] Fix some minor grammar/typos --- .../docs/project_settings/settings_project_global.md | 10 +++++----- website/docs/pype2/admin_presets_plugins.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 6c1a269b1f..b6d624caa8 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -82,8 +82,8 @@ All context filters are lists which may contain strings or Regular expressions ( - **`tasks`** - Currently processed task. `["modeling", "animation"]` :::important Filtering -Filters are optional. In case when multiple profiles match current context, profile with higher number of matched filters has higher priority that profile without filters. -(Eg. order of when filter is added doesn't matter, only the precision of matching does.) +Filters are optional. In case when multiple profiles match current context, profile with higher number of matched filters has higher priority than profile without filters. +(The order the profiles in settings doesn't matter, only the precision of matching does.) ::: ## Publish plugins @@ -94,7 +94,7 @@ Publish plugins used across all integrations. ### Extract Review Plugin responsible for automatic FFmpeg conversion to variety of formats. -Extract review is using [profile filtering](#profile-filters) to be able render different outputs for different situations. +Extract review uses [profile filtering](#profile-filters) to render different outputs for different situations. Applicable context filters: **`hosts`** - Host from which publishing was triggered. `["maya", "nuke"]` @@ -104,7 +104,7 @@ Applicable context filters: **Output Definitions** -Profile may generate multiple outputs from a single input. Each output must define unique name and output extension (use the extension without a dot e.g. **mp4**). All other settings of output definition are optional. +A profile may generate multiple outputs from a single input. Each output must define unique name and output extension (use the extension without a dot e.g. **mp4**). All other settings of output definition are optional. ![global_extract_review_output_defs](assets/global_extract_review_output_defs.png) - **`Tags`** @@ -118,7 +118,7 @@ Profile may generate multiple outputs from a single input. Each output must defi - **Output arguments** other FFmpeg output arguments like codec definition. - **`Output width`** and **`Output height`** - - it is possible to rescale output to specified resolution and keep aspect ratio. + - It is possible to rescale output to specified resolution and keep aspect ratio. - If value is set to 0, source resolution will be used. - **`Overscan crop`** diff --git a/website/docs/pype2/admin_presets_plugins.md b/website/docs/pype2/admin_presets_plugins.md index fcdc09439b..e589f7d14b 100644 --- a/website/docs/pype2/admin_presets_plugins.md +++ b/website/docs/pype2/admin_presets_plugins.md @@ -36,7 +36,7 @@ All context filters are lists which may contain strings or Regular expressions ( - **families** - Main family of processed instance. `["plate", "model"]` :::important Filtering -Filters are optional and may not be set. In case when multiple profiles match current context, profile with filters has higher priority that profile without filters. +Filters are optional and may not be set. In case when multiple profiles match current context, profile with filters has higher priority than profile without filters. ::: #### Profile outputs From aa6dc207f1cad419fd968dfd85d6d0b1923d55ba Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 00:45:50 +0100 Subject: [PATCH 07/18] Fix grammar --- openpype/hosts/blender/api/ops.py | 4 ++-- openpype/hosts/photoshop/api/launch_logic.py | 4 ++-- openpype/hosts/tvpaint/api/communication_server.py | 4 ++-- openpype/tools/utils/lib.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/blender/api/ops.py b/openpype/hosts/blender/api/ops.py index b1fa13acb9..158c32fb5a 100644 --- a/openpype/hosts/blender/api/ops.py +++ b/openpype/hosts/blender/api/ops.py @@ -84,11 +84,11 @@ class MainThreadItem: self.kwargs = kwargs def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ print("Executing process in main thread") if self.done: diff --git a/openpype/hosts/photoshop/api/launch_logic.py b/openpype/hosts/photoshop/api/launch_logic.py index 89ba6ad4e6..25732446b5 100644 --- a/openpype/hosts/photoshop/api/launch_logic.py +++ b/openpype/hosts/photoshop/api/launch_logic.py @@ -66,11 +66,11 @@ class MainThreadItem: return self._result def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ log.debug("Executing process in main thread") if self.done: diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py index e94e64e04a..6f76c25e0c 100644 --- a/openpype/hosts/tvpaint/api/communication_server.py +++ b/openpype/hosts/tvpaint/api/communication_server.py @@ -389,11 +389,11 @@ class MainThreadItem: self.kwargs = kwargs def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ log.debug("Executing process in main thread") if self.done: diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py index 8d38f03b8d..950c782727 100644 --- a/openpype/tools/utils/lib.py +++ b/openpype/tools/utils/lib.py @@ -862,11 +862,11 @@ class WrappedCallbackItem: return self._result def execute(self): - """Execute callback and store it's result. + """Execute callback and store its result. Method must be called from main thread. Item is marked as `done` when callback execution finished. Store output of callback of exception - information when callback raise one. + information when callback raises one. """ if self.done: self.log.warning("- item is already processed") From fb6c1f7f350d8f525e9db61851a0801c18f80669 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 00:48:13 +0100 Subject: [PATCH 08/18] Fix grammar --- openpype/hooks/pre_foundry_apps.py | 4 ++-- openpype/modules/example_addons/example_addon/addon.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py index 2092d5025d..21ec8e7881 100644 --- a/openpype/hooks/pre_foundry_apps.py +++ b/openpype/hooks/pre_foundry_apps.py @@ -7,7 +7,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): Nuke is executed "like" python process so it is required to pass `CREATE_NEW_CONSOLE` flag on windows to trigger creation of new console. - At the same time the newly created console won't create it's own stdout + At the same time the newly created console won't create its own stdout and stderr handlers so they should not be redirected to DEVNULL. """ @@ -18,7 +18,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): def execute(self): # Change `creationflags` to CREATE_NEW_CONSOLE - # - on Windows will nuke create new window using it's console + # - on Windows nuke will create new window using its console # Set `stdout` and `stderr` to None so new created console does not # have redirected output to DEVNULL in build self.launch_context.kwargs.update({ diff --git a/openpype/modules/example_addons/example_addon/addon.py b/openpype/modules/example_addons/example_addon/addon.py index ead647b41d..be1d3ff920 100644 --- a/openpype/modules/example_addons/example_addon/addon.py +++ b/openpype/modules/example_addons/example_addon/addon.py @@ -44,7 +44,7 @@ class AddonSettingsDef(JsonFilesSettingsDef): class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction): - """This Addon has defined it's settings and interface. + """This Addon has defined its settings and interface. This example has system settings with an enabled option. And use few other interfaces: From 47052f7445316e49dff631c91c5967ea7a60b486 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 23 Mar 2023 11:15:51 +0800 Subject: [PATCH 09/18] incrment workfile version --- .../publish/increment_workfile_version.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/increment_workfile_version.py diff --git a/openpype/hosts/max/plugins/publish/increment_workfile_version.py b/openpype/hosts/max/plugins/publish/increment_workfile_version.py new file mode 100644 index 0000000000..eda8cd7677 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/increment_workfile_version.py @@ -0,0 +1,20 @@ +import pyblish.api +from openpype.lib import version_up +from pymxs import runtime as rt + + +class IncrementWorkfileVersion(pyblish.api.ContextPlugin): + """Increment current workfile version.""" + + order = pyblish.api.IntegratorOrder + 0.9 + label = "Increment Workfile Version" + optional = True + hosts = ["max"] + families = ["workfile"] + + def process(self, context): + path = context.data["currentFile"] + filepath = version_up(path) + + rt.saveMaxFile(filepath) + self.log.info('Incrementing file version') From 2130f3d826dbb4e156f02acc680596091a1b4ce8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 23 Mar 2023 11:19:46 +0800 Subject: [PATCH 10/18] remove optional --- openpype/hosts/max/plugins/publish/increment_workfile_version.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/increment_workfile_version.py b/openpype/hosts/max/plugins/publish/increment_workfile_version.py index eda8cd7677..7b4f4e238d 100644 --- a/openpype/hosts/max/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/max/plugins/publish/increment_workfile_version.py @@ -8,7 +8,6 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder + 0.9 label = "Increment Workfile Version" - optional = True hosts = ["max"] families = ["workfile"] From 81b659e933591e3fbabc9ea1b57ce99a25ef5396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:06:01 +0100 Subject: [PATCH 11/18] Update openpype/hosts/max/plugins/publish/increment_workfile_version.py --- .../hosts/max/plugins/publish/increment_workfile_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/increment_workfile_version.py b/openpype/hosts/max/plugins/publish/increment_workfile_version.py index 7b4f4e238d..3dec214f77 100644 --- a/openpype/hosts/max/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/max/plugins/publish/increment_workfile_version.py @@ -16,4 +16,4 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin): filepath = version_up(path) rt.saveMaxFile(filepath) - self.log.info('Incrementing file version') + self.log.info("Incrementing file version") From 0a085c7001db46671d26beb0c8a59de1a37a58a2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Mar 2023 12:06:05 +0100 Subject: [PATCH 12/18] Kitsu: Slightly less strict with instance data (#4678) * Match family and families * Allow kitsu note to not have set comment and capture it without erroring in IntegrateKitsuReview * Allow fallback to context for instance `task` * Shush hound * Refactor variable names --- .../kitsu/plugins/publish/collect_kitsu_entities.py | 2 +- .../modules/kitsu/plugins/publish/integrate_kitsu_note.py | 5 ++++- .../kitsu/plugins/publish/integrate_kitsu_review.py | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index a0bd2b305b..4ea8135620 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -29,7 +29,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not zou_asset_data: raise ValueError("Zou asset data not found in OpenPype!") - task_name = instance.data.get("task") + task_name = instance.data.get("task", context.data.get("task")) if not task_name: continue diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 6fda32d85f..6be14b3bdf 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -48,7 +48,10 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): def process(self, context): for instance in context: # Check if instance is a review by checking its family - if "review" not in instance.data["families"]: + # Allow a match to primary family or any of families + families = set([instance.data["family"]] + + instance.data.get("families", [])) + if "review" not in families: continue kitsu_task = instance.data.get("kitsu_task") diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 12482b5657..e05ff05f50 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -12,17 +12,17 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): optional = True def process(self, instance): - task = instance.data["kitsu_task"]["id"] - comment = instance.data["kitsu_comment"]["id"] # Check comment has been created - if not comment: + comment_id = instance.data.get("kitsu_comment", {}).get("id") + if not comment_id: self.log.debug( "Comment not created, review not pushed to preview." ) return # Add review representations as preview of comment + task_id = instance.data["kitsu_task"]["id"] for representation in instance.data.get("representations", []): # Skip if not tagged as review if "kitsureview" not in representation.get("tags", []): @@ -31,6 +31,6 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): self.log.debug("Found review at: {}".format(review_path)) gazu.task.add_preview( - task, comment, review_path, normalize_movie=True + task_id, comment_id, review_path, normalize_movie=True ) self.log.info("Review upload on comment") From d0f083ec195a0e4ac639865c14d50635d318bb6e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 23 Mar 2023 12:52:25 +0100 Subject: [PATCH 13/18] Publisher: Explicit save (#4676) * added save button class * added save button to window * workfiles is also part of context in CreateContext to be able check if context changed * window cares about trigger of convertors * use abstractproperty with property and abstractmethod decorators * save changes happens using main window and can be blocked * fix pyside compatibility * use create context to get current context names * Fix docstring label * added shortcuts for save and reset * change control string matching for macos * added 'publish_has_started' property * allow save only if publishing did not start yet * rename 'get_selected_convertors' to 'get_selected_legacy_convertors' and added docstrings * Added Saved changes * disable instances toggle when publishing started * Fix reset button tooltip Co-authored-by: Roy Nieterau * Use QKeySequence to string for tooltips * added example output * use predefined method to emit card message --------- Co-authored-by: Roy Nieterau --- openpype/pipeline/create/context.py | 91 +++++++-- openpype/tools/publisher/constants.py | 5 +- openpype/tools/publisher/control.py | 185 +++++++++++++----- openpype/tools/publisher/widgets/__init__.py | 6 +- .../publisher/widgets/card_view_widgets.py | 19 ++ .../tools/publisher/widgets/images/save.png | Bin 0 -> 3961 bytes .../publisher/widgets/list_view_widgets.py | 27 +++ .../publisher/widgets/overview_widget.py | 38 +++- openpype/tools/publisher/widgets/widgets.py | 34 +++- openpype/tools/publisher/window.py | 127 ++++++++++-- openpype/tools/traypublisher/window.py | 2 +- 11 files changed, 450 insertions(+), 84 deletions(-) create mode 100644 openpype/tools/publisher/widgets/images/save.png diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index acc2bb054f..22cab28e4b 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -22,7 +22,7 @@ from openpype.lib.attribute_definitions import ( deserialize_attr_defs, get_default_values, ) -from openpype.host import IPublishHost +from openpype.host import IPublishHost, IWorkfileHost from openpype.pipeline import legacy_io from openpype.pipeline.plugin_discover import DiscoverResult @@ -1374,6 +1374,7 @@ class CreateContext: self._current_project_name = None self._current_asset_name = None self._current_task_name = None + self._current_workfile_path = None self._host_is_valid = host_is_valid # Currently unused variable @@ -1503,14 +1504,62 @@ class CreateContext: return os.environ["AVALON_APP"] def get_current_project_name(self): + """Project name which was used as current context on context reset. + + Returns: + Union[str, None]: Project name. + """ + return self._current_project_name def get_current_asset_name(self): + """Asset name which was used as current context on context reset. + + Returns: + Union[str, None]: Asset name. + """ + return self._current_asset_name def get_current_task_name(self): + """Task name which was used as current context on context reset. + + Returns: + Union[str, None]: Task name. + """ + return self._current_task_name + def get_current_workfile_path(self): + """Workfile path which was opened on context reset. + + Returns: + Union[str, None]: Workfile path. + """ + + return self._current_workfile_path + + @property + def context_has_changed(self): + """Host context has changed. + + As context is used project, asset, task name and workfile path if + host does support workfiles. + + Returns: + bool: Context changed. + """ + + project_name, asset_name, task_name, workfile_path = ( + self._get_current_host_context() + ) + return ( + self._current_project_name != project_name + or self._current_asset_name != asset_name + or self._current_task_name != task_name + or self._current_workfile_path != workfile_path + ) + project_name = property(get_current_project_name) @property @@ -1575,6 +1624,28 @@ class CreateContext: self._collection_shared_data = None self.refresh_thumbnails() + def _get_current_host_context(self): + project_name = asset_name = task_name = workfile_path = None + if hasattr(self.host, "get_current_context"): + host_context = self.host.get_current_context() + if host_context: + project_name = host_context.get("project_name") + asset_name = host_context.get("asset_name") + task_name = host_context.get("task_name") + + if isinstance(self.host, IWorkfileHost): + workfile_path = self.host.get_current_workfile() + + # --- TODO remove these conditions --- + if not project_name: + project_name = legacy_io.Session.get("AVALON_PROJECT") + if not asset_name: + asset_name = legacy_io.Session.get("AVALON_ASSET") + if not task_name: + task_name = legacy_io.Session.get("AVALON_TASK") + # --- + return project_name, asset_name, task_name, workfile_path + def reset_current_context(self): """Refresh current context. @@ -1593,24 +1664,14 @@ class CreateContext: are stored. We should store the workfile (if is available) too. """ - project_name = asset_name = task_name = None - if hasattr(self.host, "get_current_context"): - host_context = self.host.get_current_context() - if host_context: - project_name = host_context.get("project_name") - asset_name = host_context.get("asset_name") - task_name = host_context.get("task_name") - - if not project_name: - project_name = legacy_io.Session.get("AVALON_PROJECT") - if not asset_name: - asset_name = legacy_io.Session.get("AVALON_ASSET") - if not task_name: - task_name = legacy_io.Session.get("AVALON_TASK") + project_name, asset_name, task_name, workfile_path = ( + self._get_current_host_context() + ) self._current_project_name = project_name self._current_asset_name = asset_name self._current_task_name = task_name + self._current_workfile_path = workfile_path def reset_plugins(self, discover_publish_plugins=True): """Reload plugins. diff --git a/openpype/tools/publisher/constants.py b/openpype/tools/publisher/constants.py index b2bfd7dd5c..5d23886aa8 100644 --- a/openpype/tools/publisher/constants.py +++ b/openpype/tools/publisher/constants.py @@ -1,4 +1,4 @@ -from qtpy import QtCore +from qtpy import QtCore, QtGui # ID of context item in instance view CONTEXT_ID = "context" @@ -26,6 +26,9 @@ GROUP_ROLE = QtCore.Qt.UserRole + 7 CONVERTER_IDENTIFIER_ROLE = QtCore.Qt.UserRole + 8 CREATOR_SORT_ROLE = QtCore.Qt.UserRole + 9 +ResetKeySequence = QtGui.QKeySequence( + QtCore.Qt.ControlModifier | QtCore.Qt.Key_R +) __all__ = ( "CONTEXT_ID", diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 49e7eeb4f7..b62ae7ecc1 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -6,7 +6,7 @@ import collections import uuid import tempfile import shutil -from abc import ABCMeta, abstractmethod, abstractproperty +from abc import ABCMeta, abstractmethod import six import pyblish.api @@ -964,7 +964,8 @@ class AbstractPublisherController(object): access objects directly but by using wrappers that can be serialized. """ - @abstractproperty + @property + @abstractmethod def log(self): """Controller's logger object. @@ -974,13 +975,15 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def event_system(self): """Inner event system for publisher controller.""" pass - @abstractproperty + @property + @abstractmethod def project_name(self): """Current context project name. @@ -990,7 +993,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def current_asset_name(self): """Current context asset name. @@ -1000,7 +1004,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def current_task_name(self): """Current context task name. @@ -1010,7 +1015,21 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod + def host_context_has_changed(self): + """Host context changed after last reset. + + 'CreateContext' has this option available using 'context_has_changed'. + + Returns: + bool: Context has changed. + """ + + pass + + @property + @abstractmethod def host_is_valid(self): """Host is valid for creation part. @@ -1023,7 +1042,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def instances(self): """Collected/created instances. @@ -1134,7 +1154,13 @@ class AbstractPublisherController(object): @abstractmethod def save_changes(self): - """Save changes in create context.""" + """Save changes in create context. + + Save can crash because of unexpected errors. + + Returns: + bool: Save was successful. + """ pass @@ -1145,7 +1171,19 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod + def publish_has_started(self): + """Has publishing finished. + + Returns: + bool: If publishing finished and all plugins were iterated. + """ + + pass + + @property + @abstractmethod def publish_has_finished(self): """Has publishing finished. @@ -1155,7 +1193,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_is_running(self): """Publishing is running right now. @@ -1165,7 +1204,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_has_validated(self): """Publish validation passed. @@ -1175,7 +1215,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_has_crashed(self): """Publishing crashed for any reason. @@ -1185,7 +1226,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_has_validation_errors(self): """During validation happened at least one validation error. @@ -1195,7 +1237,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_max_progress(self): """Get maximum possible progress number. @@ -1205,7 +1248,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_progress(self): """Current progress number. @@ -1215,7 +1259,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def publish_error_msg(self): """Current error message which cause fail of publishing. @@ -1267,7 +1312,8 @@ class AbstractPublisherController(object): pass - @abstractproperty + @property + @abstractmethod def convertor_items(self): pass @@ -1356,6 +1402,7 @@ class BasePublisherController(AbstractPublisherController): self._publish_has_validation_errors = False self._publish_has_crashed = False # All publish plugins are processed + self._publish_has_started = False self._publish_has_finished = False self._publish_max_progress = 0 self._publish_progress = 0 @@ -1386,7 +1433,8 @@ class BasePublisherController(AbstractPublisherController): "show.card.message" - Show card message request (UI related). "instances.refresh.finished" - Instances are refreshed. "plugins.refresh.finished" - Plugins refreshed. - "publish.reset.finished" - Publish context reset finished. + "publish.reset.finished" - Reset finished. + "controller.reset.started" - Controller reset started. "controller.reset.finished" - Controller reset finished. "publish.process.started" - Publishing started. Can be started from paused state. @@ -1425,7 +1473,16 @@ class BasePublisherController(AbstractPublisherController): def _set_host_is_valid(self, value): if self._host_is_valid != value: self._host_is_valid = value - self._emit_event("publish.host_is_valid.changed", {"value": value}) + self._emit_event( + "publish.host_is_valid.changed", {"value": value} + ) + + def _get_publish_has_started(self): + return self._publish_has_started + + def _set_publish_has_started(self, value): + if value != self._publish_has_started: + self._publish_has_started = value def _get_publish_has_finished(self): return self._publish_has_finished @@ -1449,7 +1506,9 @@ class BasePublisherController(AbstractPublisherController): def _set_publish_has_validated(self, value): if self._publish_has_validated != value: self._publish_has_validated = value - self._emit_event("publish.has_validated.changed", {"value": value}) + self._emit_event( + "publish.has_validated.changed", {"value": value} + ) def _get_publish_has_crashed(self): return self._publish_has_crashed @@ -1497,6 +1556,9 @@ class BasePublisherController(AbstractPublisherController): host_is_valid = property( _get_host_is_valid, _set_host_is_valid ) + publish_has_started = property( + _get_publish_has_started, _set_publish_has_started + ) publish_has_finished = property( _get_publish_has_finished, _set_publish_has_finished ) @@ -1526,6 +1588,7 @@ class BasePublisherController(AbstractPublisherController): """Reset most of attributes that can be reset.""" self.publish_is_running = False + self.publish_has_started = False self.publish_has_validated = False self.publish_has_crashed = False self.publish_has_validation_errors = False @@ -1645,10 +1708,7 @@ class PublisherController(BasePublisherController): str: Project name. """ - if not hasattr(self._host, "get_current_context"): - return legacy_io.active_project() - - return self._host.get_current_context()["project_name"] + return self._create_context.get_current_project_name() @property def current_asset_name(self): @@ -1658,10 +1718,7 @@ class PublisherController(BasePublisherController): Union[str, None]: Asset name or None if asset is not set. """ - if not hasattr(self._host, "get_current_context"): - return legacy_io.Session["AVALON_ASSET"] - - return self._host.get_current_context()["asset_name"] + return self._create_context.get_current_asset_name() @property def current_task_name(self): @@ -1671,10 +1728,11 @@ class PublisherController(BasePublisherController): Union[str, None]: Task name or None if task is not set. """ - if not hasattr(self._host, "get_current_context"): - return legacy_io.Session["AVALON_TASK"] + return self._create_context.get_current_task_name() - return self._host.get_current_context()["task_name"] + @property + def host_context_has_changed(self): + return self._create_context.context_has_changed @property def instances(self): @@ -1751,6 +1809,8 @@ class PublisherController(BasePublisherController): """Reset everything related to creation and publishing.""" self.stop_publish() + self._emit_event("controller.reset.started") + self.host_is_valid = self._create_context.host_is_valid self._create_context.reset_preparation() @@ -1992,7 +2052,15 @@ class PublisherController(BasePublisherController): ) def trigger_convertor_items(self, convertor_identifiers): - self.save_changes() + """Trigger legacy item convertors. + + This functionality requires to save and reset CreateContext. The reset + is needed so Creators can collect converted items. + + Args: + convertor_identifiers (list[str]): Identifiers of convertor + plugins. + """ success = True try: @@ -2039,13 +2107,33 @@ class PublisherController(BasePublisherController): self._on_create_instance_change() return success - def save_changes(self): - """Save changes happened during creation.""" + def save_changes(self, show_message=True): + """Save changes happened during creation. + + Trigger save of changes using host api. This functionality does not + validate anything. It is required to do checks before this method is + called to be able to give user actionable response e.g. check of + context using 'host_context_has_changed'. + + Args: + show_message (bool): Show message that changes were + saved successfully. + + Returns: + bool: Save of changes was successful. + """ + if not self._create_context.host_is_valid: - return + # TODO remove + # Fake success save when host is not valid for CreateContext + # this is for testing as experimental feature + return True try: self._create_context.save_changes() + if show_message: + self.emit_card_message("Saved changes..") + return True except CreatorsOperationFailed as exc: self._emit_event( @@ -2056,16 +2144,17 @@ class PublisherController(BasePublisherController): } ) + return False + def remove_instances(self, instance_ids): """Remove instances based on instance ids. Args: instance_ids (List[str]): List of instance ids to remove. """ - # QUESTION Expect that instances are really removed? In that case save - # reset is not required and save changes too. - self.save_changes() + # QUESTION Expect that instances are really removed? In that case reset + # is not required. self._remove_instances_from_context(instance_ids) self._on_create_instance_change() @@ -2136,12 +2225,22 @@ class PublisherController(BasePublisherController): self._publish_comment_is_set = True def publish(self): - """Run publishing.""" + """Run publishing. + + Make sure all changes are saved before method is called (Call + 'save_changes' and check output). + """ + self._publish_up_validation = False self._start_publish() def validate(self): - """Run publishing and stop after Validation.""" + """Run publishing and stop after Validation. + + Make sure all changes are saved before method is called (Call + 'save_changes' and check output). + """ + if self.publish_has_validated: return self._publish_up_validation = True @@ -2152,10 +2251,8 @@ class PublisherController(BasePublisherController): if self.publish_is_running: return - # Make sure changes are saved - self.save_changes() - self.publish_is_running = True + self.publish_has_started = True self._emit_event("publish.process.started") diff --git a/openpype/tools/publisher/widgets/__init__.py b/openpype/tools/publisher/widgets/__init__.py index 042985b007..f18e6cc61e 100644 --- a/openpype/tools/publisher/widgets/__init__.py +++ b/openpype/tools/publisher/widgets/__init__.py @@ -4,8 +4,9 @@ from .icons import ( get_icon ) from .widgets import ( - StopBtn, + SaveBtn, ResetBtn, + StopBtn, ValidateBtn, PublishBtn, CreateNextPageOverlay, @@ -25,8 +26,9 @@ __all__ = ( "get_pixmap", "get_icon", - "StopBtn", + "SaveBtn", "ResetBtn", + "StopBtn", "ValidateBtn", "PublishBtn", "CreateNextPageOverlay", diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 3fd5243ce9..0734e1bc27 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -164,6 +164,11 @@ class BaseGroupWidget(QtWidgets.QWidget): def _on_widget_selection(self, instance_id, group_id, selection_type): self.selected.emit(instance_id, group_id, selection_type) + def set_active_toggle_enabled(self, enabled): + for widget in self._widgets_by_id.values(): + if isinstance(widget, InstanceCardWidget): + widget.set_active_toggle_enabled(enabled) + class ConvertorItemsGroupWidget(BaseGroupWidget): def update_items(self, items_by_id): @@ -437,6 +442,9 @@ class InstanceCardWidget(CardWidget): self.update_instance_values() + def set_active_toggle_enabled(self, enabled): + self._active_checkbox.setEnabled(enabled) + def set_active(self, new_value): """Set instance as active.""" checkbox_value = self._active_checkbox.isChecked() @@ -551,6 +559,7 @@ class InstanceCardView(AbstractInstanceView): self._context_widget = None self._convertor_items_group = None + self._active_toggle_enabled = True self._widgets_by_group = {} self._ordered_groups = [] @@ -667,6 +676,9 @@ class InstanceCardView(AbstractInstanceView): group_widget.update_instances( instances_by_group[group_name] ) + group_widget.set_active_toggle_enabled( + self._active_toggle_enabled + ) self._update_ordered_group_names() @@ -1091,3 +1103,10 @@ class InstanceCardView(AbstractInstanceView): self._explicitly_selected_groups = selected_groups self._explicitly_selected_instance_ids = selected_instances + + def set_active_toggle_enabled(self, enabled): + if self._active_toggle_enabled is enabled: + return + self._active_toggle_enabled = enabled + for group_widget in self._widgets_by_group.values(): + group_widget.set_active_toggle_enabled(enabled) diff --git a/openpype/tools/publisher/widgets/images/save.png b/openpype/tools/publisher/widgets/images/save.png new file mode 100644 index 0000000000000000000000000000000000000000..0db48d74ae2660e8766fc2bcd30ed794fd067068 GIT binary patch literal 3961 zcmeHJdpK0<8h_UehSUh1R3b4hMQ-KRm{i7P?GWigs3{d~6=y2B#o9TE+?Q*J=pHd$ zgiTCIHJe5q6>^Qit|qw@N@cG#`<&vwB2R0@;A=P@xiQ|C1sQL`1PAx(%YuWdyP27 zB^jEVZtitH@bqy0nH+SmQ^+cqzB`#W%u*>yEx}vf)uc44l)D=D=EMuD#m#L&edn@- z*+s=IuSaFg-uKhv7Y2T5+FsuLEx^_xw z)Uvw7e^kw~2b-XoU22Hb&aE`dIMJ%?pR~TiY?Wbp*w*f$DRs?&u`Szp)if5jcJC-H z`k#K|VIFTri9L}Ea`Ze5V5miYNXSK-U}ER;QTDpcA$jk+nu`aHsy!NQb?Q%$x|Aqw z)~`{-zH{XyUWu-|f8x2fUg^)CO6+*PUgvFYYNg(nQ5ANn6GeXZ?L=9->hznG!``x) zN=tohE;XDfd^XIQ%sX7yGu}TPX5Aa4(lgE)dZL!yJRVs)xHjGOHuBZ`^^eImUM{P$o0dA%vq1q6V>3+O0SU*&fJZ-yOzxoXwjh9n{Ng03{fFN zL8pp)VF);{*!Qe`)gguX;(ng?#ma2}JAZw)(II&P^An-+N zP5HhEe+Qe-r>lZ)nsvS?3E78Wp=>@zR|X56P*cGi>Jtjk6cMVY8LY`^H=4PI_#v2G zEQXXQks?c?K2v#Cn!PER-#*@LG_4*C9Hnv6N*%}I85M`ZQhdq}fc1Q%DJq8V%K~L%YgEywAyl}D9;@)#G^>zCt zz+T5;R)ani%N)@F<8tdNc}Tc7fU%l7kV>ohQ4m~kmH_ft$4hf*NZ24%(^vx|tv2AU zNikds;7|OrRp2toElHM*1vuw^T&gKtwYREija*9Cct_LsZ`hk(mkIXfs_&-!+@on} z1j4d|vtojE1p2w^lm##JN4H-u+-%P;+DV%Y?ps2w)&i{EB_#`xef2aT05k5&2vG^$ zS2Zb80AK3_4j2G-*LgJw#ep{!H2^rZs69!Ib~Yrb2KzQisDDq3BQ-RhVUzkix`h(a z7vdul*d>(6)VB*{=K=(UFjYu9F*n>LvX)GV*_d)=2@LUCLUJG8cp-t-UaAsUo*{t| zUMk2Va~8PvZ^mintOv1?nWD*D^%K{FO(?@oW1TU6iok#6ohy3gtYA`9$A%!@^*G2P z9KU;~k4t*~sUw7Rsx2W=;?$FN4+$(uWRu({Uf~i*4@e{f>hL`%FYNw|j=tde9b>7ImpXdy!a^KB(b={jPE7wCR&Tdpflc;WM5 z!`L1?tubGZaIwOtKs3sDlxc+$HioOU1m_#`Gzj{l+8og@b{`Cg>1uV9yE~OhT=3aR z0cqBikJY3BoEj@k!;kU3iL_lBprW;rPtOw0IyJX)`2mPz_BZrU!7&;UP|K0lnrL#} zn_-5-1e3IyBV>Gj5J@BTv|gTMk_LYjijS=&d^mDEq>f6ie)l|*Mk>luBl-k`D88oy z(`s`8TpfuTT1x0WNpR;W{l60meRI{}%4jfti#R0gz=L7n5d-jYzyd98|2UVQu;DKa zfb<4)i!czrf1Kw#)Jhz3<4jg-7*V(9Qi!TtPwc@gxqqep2jPE_59oef z)4{2j&ivX%T~%$AI};WDQw6IX`H}1&wf5tv{dvj%mp2X3kJrW-=~NwUu?2B#@_{f+ z#$j+Vv+m9^z;1}EURD#m;V;hcFhIO@d=+svc(ZsDz;u< zn%}M{gTPC_o^o%p&t1Pw9TCe$1H2n=ilikqS-tVcv(oa^!xjPOh+kSB%8e|G9uJ1^ zX`;)%PEwFC94EdtrAv!a>Q$~6QlLc8X%^9~jWMk9MdgfbbaOfw-?2!GFc#QMR@bj% zLZI&Fdnw8YoS%*yH25yK#{_}6Y{o{XDl9e*Ft@w;qAyLs$i%07Y4zW|_c4)zg&aNL zc=t3>0ta^Pp=qEljL@Zmqo>Zu!h#nCcxSeB?X$^qt>AscP+%*ec3Iry_Z1if%k>uZ z?43A$e`BesA{bx$p#51`bZo~*CB9vjcIa|}+J<}@Bn-V_6gZES*=_VN2VArVA4==w z-LC>Z?|X_UlM&@Lc)@NbHRx)cPL;kuRX 0: dialog_x -= diff @@ -549,6 +633,14 @@ class PublisherWindow(QtWidgets.QDialog): def _on_create_request(self): self._go_to_create_tab() + def _on_convert_requested(self): + if not self._save_changes(False): + return + convertor_identifiers = ( + self._overview_widget.get_selected_legacy_convertors() + ) + self._controller.trigger_convertor_items(convertor_identifiers) + def _set_current_tab(self, identifier): self._tabs_widget.set_current_tab(identifier) @@ -599,8 +691,10 @@ class PublisherWindow(QtWidgets.QDialog): self._publish_frame.setVisible(visible) self._update_publish_frame_rect() + def _on_save_clicked(self): + self._save_changes(True) + def _on_reset_clicked(self): - self.save_changes() self.reset() def _on_stop_clicked(self): @@ -610,14 +704,17 @@ class PublisherWindow(QtWidgets.QDialog): self._controller.set_comment(self._comment_input.text()) def _on_validate_clicked(self): - self._set_publish_comment() - self._controller.validate() + if self._save_changes(False): + self._set_publish_comment() + self._controller.validate() def _on_publish_clicked(self): - self._set_publish_comment() - self._controller.publish() + if self._save_changes(False): + self._set_publish_comment() + self._controller.publish() def _set_footer_enabled(self, enabled): + self._save_btn.setEnabled(True) self._reset_btn.setEnabled(True) if enabled: self._stop_btn.setEnabled(False) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 3007fa66a5..3ac1b4c4ad 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -247,7 +247,7 @@ class TrayPublishWindow(PublisherWindow): def _on_project_select(self, project_name): # TODO register project specific plugin paths - self._controller.save_changes() + self._controller.save_changes(False) self._controller.reset_project_data_cache() self.reset() From 79eb8f105f02c9309949d64d425f64095ac6e7c5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 12:54:55 +0100 Subject: [PATCH 14/18] celaction: make parameters enumerator --- .../celaction/hooks/pre_celaction_setup.py | 16 ++++++++++++++-- .../defaults/project_settings/celaction.json | 10 ++++------ .../schema_project_celaction.json | 19 +++++++++++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/celaction/hooks/pre_celaction_setup.py b/openpype/hosts/celaction/hooks/pre_celaction_setup.py index 7506f1a52c..81f5f2bf11 100644 --- a/openpype/hosts/celaction/hooks/pre_celaction_setup.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_setup.py @@ -56,8 +56,20 @@ class CelactionPrelaunchHook(PreLaunchHook): ] # Add custom parameters from workfile settings - if workfile_settings["parameters"]: - parameters += workfile_settings["parameters"] + if "render_chunk" in workfile_settings["submission_overrides"]: + parameters += [ + "--chunk *CHUNK*" + ] + elif "resolution" in workfile_settings["submission_overrides"]: + parameters += [ + "--resolutionWidth *X*", + "--resolutionHeight *Y*" + ] + elif "frame_range" in workfile_settings["submission_overrides"]: + parameters += [ + "--frameStart *START*", + "--frameEnd *END*" + ] winreg.SetValueEx( hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, diff --git a/openpype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json index 0194b2c48c..822604fd2f 100644 --- a/openpype/settings/defaults/project_settings/celaction.json +++ b/openpype/settings/defaults/project_settings/celaction.json @@ -10,12 +10,10 @@ } }, "workfile": { - "parameters": [ - "--chunk *CHUNK*", - "--frameStart *START*", - "--frameEnd *END*", - "--resolutionWidth *X*", - "--resolutionHeight *Y*" + "submission_overrides": [ + "render_chunk", + "frame_range", + "resolution" ] }, "publish": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index 4ca3cbb4da..c5ca3eb9f5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -29,10 +29,21 @@ "label": "Workfile", "children": [ { - "key": "parameters", - "label": "Parameters", - "type": "list", - "object_type": "text" + "key": "submission_overrides", + "label": "Submission workfile overrides", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "render_chunk": "Pass chunk size" + }, + { + "frame_range": "Pass frame range" + }, + { + "resolution": "Pass resolution" + } + ] } ] }, From 011e635c1f91a3e49b282aaf3bf935da3ba31bd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 12:56:54 +0100 Subject: [PATCH 15/18] hound --- openpype/hosts/celaction/hooks/pre_celaction_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/celaction/hooks/pre_celaction_setup.py b/openpype/hosts/celaction/hooks/pre_celaction_setup.py index 81f5f2bf11..c7e8bc5d0c 100644 --- a/openpype/hosts/celaction/hooks/pre_celaction_setup.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_setup.py @@ -58,8 +58,8 @@ class CelactionPrelaunchHook(PreLaunchHook): # Add custom parameters from workfile settings if "render_chunk" in workfile_settings["submission_overrides"]: parameters += [ - "--chunk *CHUNK*" - ] + "--chunk *CHUNK*" + ] elif "resolution" in workfile_settings["submission_overrides"]: parameters += [ "--resolutionWidth *X*", From f5117f4593f62b086e6af1809331c5d3580af73c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 14:28:05 +0100 Subject: [PATCH 16/18] celaction: fixes after testing --- .../hosts/celaction/hooks/pre_celaction_setup.py | 16 +++++++++------- .../plugins/publish/submit_celaction_deadline.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/celaction/hooks/pre_celaction_setup.py b/openpype/hosts/celaction/hooks/pre_celaction_setup.py index c7e8bc5d0c..96e784875c 100644 --- a/openpype/hosts/celaction/hooks/pre_celaction_setup.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_setup.py @@ -58,17 +58,17 @@ class CelactionPrelaunchHook(PreLaunchHook): # Add custom parameters from workfile settings if "render_chunk" in workfile_settings["submission_overrides"]: parameters += [ - "--chunk *CHUNK*" + "--chunk", "*CHUNK*" ] - elif "resolution" in workfile_settings["submission_overrides"]: + if "resolution" in workfile_settings["submission_overrides"]: parameters += [ - "--resolutionWidth *X*", - "--resolutionHeight *Y*" + "--resolutionWidth", "*X*", + "--resolutionHeight", "*Y*" ] - elif "frame_range" in workfile_settings["submission_overrides"]: + if "frame_range" in workfile_settings["submission_overrides"]: parameters += [ - "--frameStart *START*", - "--frameEnd *END*" + "--frameStart", "*START*", + "--frameEnd", "*END*" ] winreg.SetValueEx( @@ -76,6 +76,8 @@ class CelactionPrelaunchHook(PreLaunchHook): subprocess.list2cmdline(parameters) ) + self.log.debug(f"__ parameters: \"{parameters}\"") + # setting resolution parameters path_submit = "\\".join([ path_user_settings, "Dialogs", "SubmitOutput" diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index 038ee4fc03..bcf0850768 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -106,7 +106,7 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): # define chunk and priority chunk_size = instance.context.data.get("chunk") - if chunk_size == 0: + if not chunk_size: chunk_size = self.deadline_chunk_size # search for %02d pattern in name, and padding number From 0f6d969867821c077486cd685bb0c8efbeabac21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 23 Mar 2023 14:35:58 +0100 Subject: [PATCH 17/18] fix shortcut key sequence check (#4694) --- openpype/tools/publisher/window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index c72b60d826..8826e0f849 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -439,7 +439,9 @@ class PublisherWindow(QtWidgets.QDialog): event.accept() return - if event.matches(ResetKeySequence): + if ResetKeySequence.matches( + QtGui.QKeySequence(event.key() | event.modifiers()) + ): if not self.controller.publish_is_running: self.reset() event.accept() From 8b9a3547d9feeb9516a119f08b6b3e5964abcee9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 17:15:40 +0100 Subject: [PATCH 18/18] adding project actions --- .github/workflows/project_actions.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/project_actions.yml diff --git a/.github/workflows/project_actions.yml b/.github/workflows/project_actions.yml new file mode 100644 index 0000000000..9c8c9da7dd --- /dev/null +++ b/.github/workflows/project_actions.yml @@ -0,0 +1,23 @@ +name: project-actions + +on: + pull_request: + types: [review_requested, closed] + pull_request_review: + types: [submitted] + +jobs: + pr_review_requested: + name: pr_review_requested + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.action == 'review_requested' + steps: + - name: Move PR to 'Change Requested' + uses: leonsteinhaeuser/project-beta-automations@v2.1.0 + with: + gh_token: ${{ secrets.YNPUT_BOT_TOKEN }} + user: ${{ secrets.CI_USER }} + organization: ynput + project_id: 11 + resource_node_id: ${{ github.event.pull_request.node_id }} + status_value: Change Requested