From 2b37b8af48b58d61ce13b1228a6499b283bcf1bc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:08:42 +0200 Subject: [PATCH] AYON: Addon settings in OpenPype (#5347) * copied addons from 'ayon-addon-settings' * added AE, photoshop and harmony addon * moved openpype to subfolder * cleanup repository files * updated create package script and README.md * formatting fixes * added cli flags to be able keep server structure * print progress and output dir * another formatting fixes --- .gitignore | 2 +- server_addon/README.md | 23 +- server_addon/aftereffects/LICENSE | 202 +++ server_addon/aftereffects/README.md | 4 + server_addon/aftereffects/server/__init__.py | 15 + .../aftereffects/server/settings/__init__.py | 10 + .../server/settings/creator_plugins.py | 16 + .../aftereffects/server/settings/imageio.py | 48 + .../aftereffects/server/settings/main.py | 62 + .../server/settings/publish_plugins.py | 36 + .../server/settings/workfile_builder.py | 25 + server_addon/aftereffects/server/version.py | 3 + server_addon/applications/server/__init__.py | 153 ++ .../applications/server/applications.json | 1125 +++++++++++++++ server_addon/applications/server/settings.py | 201 +++ server_addon/applications/server/tools.json | 55 + server_addon/applications/server/version.py | 1 + server_addon/blender/server/__init__.py | 19 + .../blender/server/settings/__init__.py | 10 + .../blender/server/settings/imageio.py | 48 + server_addon/blender/server/settings/main.py | 53 + .../server/settings/publish_plugins.py | 273 ++++ server_addon/blender/server/version.py | 1 + server_addon/celaction/server/__init__.py | 19 + server_addon/celaction/server/imageio.py | 48 + server_addon/celaction/server/settings.py | 92 ++ server_addon/celaction/server/version.py | 1 + server_addon/clockify/server/__init__.py | 15 + server_addon/clockify/server/settings.py | 9 + server_addon/clockify/server/version.py | 1 + server_addon/core/server/__init__.py | 14 + server_addon/core/server/settings/__init__.py | 7 + server_addon/core/server/settings/main.py | 160 +++ .../core/server/settings/publish_plugins.py | 959 +++++++++++++ server_addon/core/server/settings/tools.py | 506 +++++++ server_addon/core/server/version.py | 1 + server_addon/create_ayon_addon.py | 140 -- server_addon/create_ayon_addons.py | 279 ++++ server_addon/deadline/server/__init__.py | 17 + .../deadline/server/settings/__init__.py | 10 + server_addon/deadline/server/settings/main.py | 48 + .../server/settings/publish_plugins.py | 435 ++++++ server_addon/deadline/server/version.py | 1 + server_addon/flame/server/__init__.py | 19 + .../flame/server/settings/__init__.py | 10 + .../flame/server/settings/create_plugins.py | 120 ++ server_addon/flame/server/settings/imageio.py | 130 ++ .../flame/server/settings/loader_plugins.py | 99 ++ server_addon/flame/server/settings/main.py | 33 + .../flame/server/settings/publish_plugins.py | 190 +++ server_addon/flame/server/version.py | 1 + server_addon/fusion/server/__init__.py | 19 + server_addon/fusion/server/imageio.py | 48 + server_addon/fusion/server/settings.py | 95 ++ server_addon/fusion/server/version.py | 1 + server_addon/harmony/LICENSE | 202 +++ server_addon/harmony/README.md | 4 + server_addon/harmony/server/__init__.py | 15 + .../harmony/server/settings/__init__.py | 10 + .../harmony/server/settings/imageio.py | 55 + server_addon/harmony/server/settings/load.py | 20 + server_addon/harmony/server/settings/main.py | 68 + .../server/settings/publish_plugins.py | 76 + server_addon/harmony/server/version.py | 3 + server_addon/hiero/server/__init__.py | 19 + .../hiero/server/settings/__init__.py | 10 + server_addon/hiero/server/settings/common.py | 98 ++ .../hiero/server/settings/create_plugins.py | 97 ++ server_addon/hiero/server/settings/filters.py | 19 + server_addon/hiero/server/settings/imageio.py | 169 +++ .../hiero/server/settings/loader_plugins.py | 38 + server_addon/hiero/server/settings/main.py | 64 + .../hiero/server/settings/publish_plugins.py | 48 + .../hiero/server/settings/scriptsmenu.py | 41 + server_addon/hiero/server/version.py | 1 + server_addon/houdini/server/__init__.py | 17 + .../houdini/server/settings/__init__.py | 10 + .../houdini/server/settings/imageio.py | 48 + server_addon/houdini/server/settings/main.py | 79 ++ .../server/settings/publish_plugins.py | 150 ++ server_addon/houdini/server/version.py | 1 + server_addon/kitsu/server/__init__.py | 19 + server_addon/kitsu/server/settings.py | 111 ++ server_addon/kitsu/server/version.py | 1 + server_addon/maya/LICENCE | 201 +++ server_addon/maya/README.md | 4 + server_addon/maya/server/__init__.py | 16 + server_addon/maya/server/settings/__init__.py | 0 server_addon/maya/server/settings/creators.py | 408 ++++++ .../settings/explicit_plugins_loading.py | 429 ++++++ server_addon/maya/server/settings/imageio.py | 126 ++ .../maya/server/settings/include_handles.py | 30 + server_addon/maya/server/settings/loaders.py | 115 ++ server_addon/maya/server/settings/main.py | 139 ++ .../maya/server/settings/maya_dirmap.py | 40 + .../maya/server/settings/publish_playblast.py | 382 +++++ .../maya/server/settings/publishers.py | 1262 +++++++++++++++++ .../maya/server/settings/render_settings.py | 500 +++++++ .../maya/server/settings/scriptsmenu.py | 43 + .../settings/templated_workfile_settings.py | 25 + .../settings/workfile_build_settings.py | 131 ++ server_addon/maya/server/version.py | 3 + server_addon/muster/server/__init__.py | 17 + server_addon/muster/server/settings.py | 37 + server_addon/muster/server/version.py | 1 + server_addon/nuke/server/__init__.py | 17 + server_addon/nuke/server/settings/__init__.py | 10 + server_addon/nuke/server/settings/common.py | 128 ++ .../nuke/server/settings/create_plugins.py | 223 +++ server_addon/nuke/server/settings/dirmap.py | 47 + server_addon/nuke/server/settings/filters.py | 19 + server_addon/nuke/server/settings/general.py | 42 + server_addon/nuke/server/settings/gizmo.py | 79 ++ server_addon/nuke/server/settings/imageio.py | 410 ++++++ .../nuke/server/settings/loader_plugins.py | 80 ++ server_addon/nuke/server/settings/main.py | 128 ++ .../nuke/server/settings/publish_plugins.py | 536 +++++++ .../nuke/server/settings/scriptsmenu.py | 54 + .../settings/templated_workfile_build.py | 33 + .../nuke/server/settings/workfile_builder.py | 72 + server_addon/nuke/server/version.py | 1 + .../{ => openpype}/client/pyproject.toml | 0 .../{ => openpype}/server/__init__.py | 0 server_addon/photoshop/LICENSE | 202 +++ server_addon/photoshop/README.md | 4 + server_addon/photoshop/server/__init__.py | 15 + .../photoshop/server/settings/__init__.py | 10 + .../server/settings/creator_plugins.py | 79 ++ .../photoshop/server/settings/imageio.py | 64 + .../photoshop/server/settings/main.py | 41 + .../server/settings/publish_plugins.py | 221 +++ .../server/settings/workfile_builder.py | 41 + server_addon/photoshop/server/version.py | 3 + server_addon/resolve/server/__init__.py | 19 + server_addon/resolve/server/imageio.py | 64 + server_addon/resolve/server/settings.py | 114 ++ server_addon/resolve/server/version.py | 1 + server_addon/royal_render/server/__init__.py | 17 + server_addon/royal_render/server/settings.py | 53 + server_addon/royal_render/server/version.py | 1 + .../timers_manager/server/__init__.py | 13 + .../timers_manager/server/settings.py | 9 + server_addon/timers_manager/server/version.py | 1 + server_addon/traypublisher/server/LICENSE | 202 +++ server_addon/traypublisher/server/README.md | 4 + server_addon/traypublisher/server/__init__.py | 15 + .../traypublisher/server/settings/__init__.py | 10 + .../server/settings/creator_plugins.py | 46 + .../server/settings/editorial_creators.py | 181 +++ .../traypublisher/server/settings/imageio.py | 48 + .../traypublisher/server/settings/main.py | 52 + .../server/settings/publish_plugins.py | 41 + .../server/settings/simple_creators.py | 292 ++++ server_addon/traypublisher/server/version.py | 3 + server_addon/tvpaint/server/__init__.py | 17 + .../tvpaint/server/settings/__init__.py | 10 + .../tvpaint/server/settings/create_plugins.py | 133 ++ .../tvpaint/server/settings/filters.py | 19 + .../tvpaint/server/settings/imageio.py | 48 + server_addon/tvpaint/server/settings/main.py | 90 ++ .../server/settings/publish_plugins.py | 132 ++ .../server/settings/workfile_builder.py | 30 + server_addon/tvpaint/server/version.py | 1 + server_addon/unreal/server/__init__.py | 19 + server_addon/unreal/server/imageio.py | 48 + server_addon/unreal/server/settings.py | 64 + server_addon/unreal/server/version.py | 1 + 167 files changed, 15525 insertions(+), 146 deletions(-) create mode 100644 server_addon/aftereffects/LICENSE create mode 100644 server_addon/aftereffects/README.md create mode 100644 server_addon/aftereffects/server/__init__.py create mode 100644 server_addon/aftereffects/server/settings/__init__.py create mode 100644 server_addon/aftereffects/server/settings/creator_plugins.py create mode 100644 server_addon/aftereffects/server/settings/imageio.py create mode 100644 server_addon/aftereffects/server/settings/main.py create mode 100644 server_addon/aftereffects/server/settings/publish_plugins.py create mode 100644 server_addon/aftereffects/server/settings/workfile_builder.py create mode 100644 server_addon/aftereffects/server/version.py create mode 100644 server_addon/applications/server/__init__.py create mode 100644 server_addon/applications/server/applications.json create mode 100644 server_addon/applications/server/settings.py create mode 100644 server_addon/applications/server/tools.json create mode 100644 server_addon/applications/server/version.py create mode 100644 server_addon/blender/server/__init__.py create mode 100644 server_addon/blender/server/settings/__init__.py create mode 100644 server_addon/blender/server/settings/imageio.py create mode 100644 server_addon/blender/server/settings/main.py create mode 100644 server_addon/blender/server/settings/publish_plugins.py create mode 100644 server_addon/blender/server/version.py create mode 100644 server_addon/celaction/server/__init__.py create mode 100644 server_addon/celaction/server/imageio.py create mode 100644 server_addon/celaction/server/settings.py create mode 100644 server_addon/celaction/server/version.py create mode 100644 server_addon/clockify/server/__init__.py create mode 100644 server_addon/clockify/server/settings.py create mode 100644 server_addon/clockify/server/version.py create mode 100644 server_addon/core/server/__init__.py create mode 100644 server_addon/core/server/settings/__init__.py create mode 100644 server_addon/core/server/settings/main.py create mode 100644 server_addon/core/server/settings/publish_plugins.py create mode 100644 server_addon/core/server/settings/tools.py create mode 100644 server_addon/core/server/version.py delete mode 100644 server_addon/create_ayon_addon.py create mode 100644 server_addon/create_ayon_addons.py create mode 100644 server_addon/deadline/server/__init__.py create mode 100644 server_addon/deadline/server/settings/__init__.py create mode 100644 server_addon/deadline/server/settings/main.py create mode 100644 server_addon/deadline/server/settings/publish_plugins.py create mode 100644 server_addon/deadline/server/version.py create mode 100644 server_addon/flame/server/__init__.py create mode 100644 server_addon/flame/server/settings/__init__.py create mode 100644 server_addon/flame/server/settings/create_plugins.py create mode 100644 server_addon/flame/server/settings/imageio.py create mode 100644 server_addon/flame/server/settings/loader_plugins.py create mode 100644 server_addon/flame/server/settings/main.py create mode 100644 server_addon/flame/server/settings/publish_plugins.py create mode 100644 server_addon/flame/server/version.py create mode 100644 server_addon/fusion/server/__init__.py create mode 100644 server_addon/fusion/server/imageio.py create mode 100644 server_addon/fusion/server/settings.py create mode 100644 server_addon/fusion/server/version.py create mode 100644 server_addon/harmony/LICENSE create mode 100644 server_addon/harmony/README.md create mode 100644 server_addon/harmony/server/__init__.py create mode 100644 server_addon/harmony/server/settings/__init__.py create mode 100644 server_addon/harmony/server/settings/imageio.py create mode 100644 server_addon/harmony/server/settings/load.py create mode 100644 server_addon/harmony/server/settings/main.py create mode 100644 server_addon/harmony/server/settings/publish_plugins.py create mode 100644 server_addon/harmony/server/version.py create mode 100644 server_addon/hiero/server/__init__.py create mode 100644 server_addon/hiero/server/settings/__init__.py create mode 100644 server_addon/hiero/server/settings/common.py create mode 100644 server_addon/hiero/server/settings/create_plugins.py create mode 100644 server_addon/hiero/server/settings/filters.py create mode 100644 server_addon/hiero/server/settings/imageio.py create mode 100644 server_addon/hiero/server/settings/loader_plugins.py create mode 100644 server_addon/hiero/server/settings/main.py create mode 100644 server_addon/hiero/server/settings/publish_plugins.py create mode 100644 server_addon/hiero/server/settings/scriptsmenu.py create mode 100644 server_addon/hiero/server/version.py create mode 100644 server_addon/houdini/server/__init__.py create mode 100644 server_addon/houdini/server/settings/__init__.py create mode 100644 server_addon/houdini/server/settings/imageio.py create mode 100644 server_addon/houdini/server/settings/main.py create mode 100644 server_addon/houdini/server/settings/publish_plugins.py create mode 100644 server_addon/houdini/server/version.py create mode 100644 server_addon/kitsu/server/__init__.py create mode 100644 server_addon/kitsu/server/settings.py create mode 100644 server_addon/kitsu/server/version.py create mode 100644 server_addon/maya/LICENCE create mode 100644 server_addon/maya/README.md create mode 100644 server_addon/maya/server/__init__.py create mode 100644 server_addon/maya/server/settings/__init__.py create mode 100644 server_addon/maya/server/settings/creators.py create mode 100644 server_addon/maya/server/settings/explicit_plugins_loading.py create mode 100644 server_addon/maya/server/settings/imageio.py create mode 100644 server_addon/maya/server/settings/include_handles.py create mode 100644 server_addon/maya/server/settings/loaders.py create mode 100644 server_addon/maya/server/settings/main.py create mode 100644 server_addon/maya/server/settings/maya_dirmap.py create mode 100644 server_addon/maya/server/settings/publish_playblast.py create mode 100644 server_addon/maya/server/settings/publishers.py create mode 100644 server_addon/maya/server/settings/render_settings.py create mode 100644 server_addon/maya/server/settings/scriptsmenu.py create mode 100644 server_addon/maya/server/settings/templated_workfile_settings.py create mode 100644 server_addon/maya/server/settings/workfile_build_settings.py create mode 100644 server_addon/maya/server/version.py create mode 100644 server_addon/muster/server/__init__.py create mode 100644 server_addon/muster/server/settings.py create mode 100644 server_addon/muster/server/version.py create mode 100644 server_addon/nuke/server/__init__.py create mode 100644 server_addon/nuke/server/settings/__init__.py create mode 100644 server_addon/nuke/server/settings/common.py create mode 100644 server_addon/nuke/server/settings/create_plugins.py create mode 100644 server_addon/nuke/server/settings/dirmap.py create mode 100644 server_addon/nuke/server/settings/filters.py create mode 100644 server_addon/nuke/server/settings/general.py create mode 100644 server_addon/nuke/server/settings/gizmo.py create mode 100644 server_addon/nuke/server/settings/imageio.py create mode 100644 server_addon/nuke/server/settings/loader_plugins.py create mode 100644 server_addon/nuke/server/settings/main.py create mode 100644 server_addon/nuke/server/settings/publish_plugins.py create mode 100644 server_addon/nuke/server/settings/scriptsmenu.py create mode 100644 server_addon/nuke/server/settings/templated_workfile_build.py create mode 100644 server_addon/nuke/server/settings/workfile_builder.py create mode 100644 server_addon/nuke/server/version.py rename server_addon/{ => openpype}/client/pyproject.toml (100%) rename server_addon/{ => openpype}/server/__init__.py (100%) create mode 100644 server_addon/photoshop/LICENSE create mode 100644 server_addon/photoshop/README.md create mode 100644 server_addon/photoshop/server/__init__.py create mode 100644 server_addon/photoshop/server/settings/__init__.py create mode 100644 server_addon/photoshop/server/settings/creator_plugins.py create mode 100644 server_addon/photoshop/server/settings/imageio.py create mode 100644 server_addon/photoshop/server/settings/main.py create mode 100644 server_addon/photoshop/server/settings/publish_plugins.py create mode 100644 server_addon/photoshop/server/settings/workfile_builder.py create mode 100644 server_addon/photoshop/server/version.py create mode 100644 server_addon/resolve/server/__init__.py create mode 100644 server_addon/resolve/server/imageio.py create mode 100644 server_addon/resolve/server/settings.py create mode 100644 server_addon/resolve/server/version.py create mode 100644 server_addon/royal_render/server/__init__.py create mode 100644 server_addon/royal_render/server/settings.py create mode 100644 server_addon/royal_render/server/version.py create mode 100644 server_addon/timers_manager/server/__init__.py create mode 100644 server_addon/timers_manager/server/settings.py create mode 100644 server_addon/timers_manager/server/version.py create mode 100644 server_addon/traypublisher/server/LICENSE create mode 100644 server_addon/traypublisher/server/README.md create mode 100644 server_addon/traypublisher/server/__init__.py create mode 100644 server_addon/traypublisher/server/settings/__init__.py create mode 100644 server_addon/traypublisher/server/settings/creator_plugins.py create mode 100644 server_addon/traypublisher/server/settings/editorial_creators.py create mode 100644 server_addon/traypublisher/server/settings/imageio.py create mode 100644 server_addon/traypublisher/server/settings/main.py create mode 100644 server_addon/traypublisher/server/settings/publish_plugins.py create mode 100644 server_addon/traypublisher/server/settings/simple_creators.py create mode 100644 server_addon/traypublisher/server/version.py create mode 100644 server_addon/tvpaint/server/__init__.py create mode 100644 server_addon/tvpaint/server/settings/__init__.py create mode 100644 server_addon/tvpaint/server/settings/create_plugins.py create mode 100644 server_addon/tvpaint/server/settings/filters.py create mode 100644 server_addon/tvpaint/server/settings/imageio.py create mode 100644 server_addon/tvpaint/server/settings/main.py create mode 100644 server_addon/tvpaint/server/settings/publish_plugins.py create mode 100644 server_addon/tvpaint/server/settings/workfile_builder.py create mode 100644 server_addon/tvpaint/server/version.py create mode 100644 server_addon/unreal/server/__init__.py create mode 100644 server_addon/unreal/server/imageio.py create mode 100644 server_addon/unreal/server/settings.py create mode 100644 server_addon/unreal/server/version.py diff --git a/.gitignore b/.gitignore index e5019a4e74..622d55fb88 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ Temporary Items ########### /build /dist/ -/server_addon/package/* +/server_addon/packages/* /vendor/bin/* /vendor/python/* diff --git a/server_addon/README.md b/server_addon/README.md index fa9a6001d2..c6d467adaa 100644 --- a/server_addon/README.md +++ b/server_addon/README.md @@ -1,5 +1,5 @@ -# OpenPype addon for AYON server -Convert openpype into AYON addon which can be installed on AYON server. The versioning of the addon is following versioning of OpenPype. +# Addons for AYON server +Preparation of AYON addons based on OpenPype codebase. The output is a bunch of zip files in `./packages` directory that can be uploaded to AYON server. One of the packages is `openpype` which is OpenPype code converted to AYON addon. The addon is must have requirement to be able to use `ayon-launcher`. The versioning of `openpype` addon is following versioning of OpenPype. The other addons contain only settings models. ## Intro OpenPype is transitioning to AYON, a dedicated server with its own database, moving away from MongoDB. During this transition period, OpenPype will remain compatible with both MongoDB and AYON. However, we will gradually update the codebase to align with AYON's data structure and separate individual components into addons. @@ -11,11 +11,24 @@ Since the implementation of the AYON Launcher is not yet fully completed, we wil During this transitional period, the AYON Launcher addon will be a requirement as the entry point for using the AYON Launcher. ## How to start -There is a `create_ayon_addon.py` python file which contains logic how to create server addon from OpenPype codebase. Just run the code. +There is a `create_ayon_addons.py` python file which contains logic how to create server addon from OpenPype codebase. Just run the code. ```shell -./.poetry/bin/poetry run python ./server_addon/create_ayon_addon.py +./.poetry/bin/poetry run python ./server_addon/create_ayon_addons.py ``` -It will create directory `./package/openpype//*` folder with all files necessary for AYON server. You can then copy `./package/openpype/` to server addons, or zip the folder and upload it to AYON server. Restart server to update addons information, add the addon version to server bundle and set the bundle for production or staging usage. +It will create directory `./packages/.zip` files for AYON server. You can then copy upload the zip files to AYON server. Restart server to update addons information, add the addon version to server bundle and set the bundle for production or staging usage. Once addon is on server and is enabled, you can just run AYON launcher. Content will be downloaded and used automatically. + +### Additional arguments +Additional arguments are useful for development purposes. + +To skip zip creation to keep only server ready folder structure, pass `--skip-zip` argument. +```shell +./.poetry/bin/poetry run python ./server_addon/create_ayon_addons.py --skip-zip +``` + +To create both zips and keep folder structure, pass `--keep-sources` argument. +```shell +./.poetry/bin/poetry run python ./server_addon/create_ayon_addons.py --keep-sources +``` diff --git a/server_addon/aftereffects/LICENSE b/server_addon/aftereffects/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/server_addon/aftereffects/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/server_addon/aftereffects/README.md b/server_addon/aftereffects/README.md new file mode 100644 index 0000000000..b2f34f3407 --- /dev/null +++ b/server_addon/aftereffects/README.md @@ -0,0 +1,4 @@ +AfterEffects Addon +=============== + +Integration with Adobe AfterEffects. diff --git a/server_addon/aftereffects/server/__init__.py b/server_addon/aftereffects/server/__init__.py new file mode 100644 index 0000000000..e895c07ce1 --- /dev/null +++ b/server_addon/aftereffects/server/__init__.py @@ -0,0 +1,15 @@ +from ayon_server.addons import BaseServerAddon + +from .settings import AfterEffectsSettings, DEFAULT_AFTEREFFECTS_SETTING +from .version import __version__ + + +class AfterEffects(BaseServerAddon): + name = "aftereffects" + version = __version__ + + settings_model = AfterEffectsSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_AFTEREFFECTS_SETTING) diff --git a/server_addon/aftereffects/server/settings/__init__.py b/server_addon/aftereffects/server/settings/__init__.py new file mode 100644 index 0000000000..4e96804b4a --- /dev/null +++ b/server_addon/aftereffects/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + AfterEffectsSettings, + DEFAULT_AFTEREFFECTS_SETTING, +) + + +__all__ = ( + "AfterEffectsSettings", + "DEFAULT_AFTEREFFECTS_SETTING", +) diff --git a/server_addon/aftereffects/server/settings/creator_plugins.py b/server_addon/aftereffects/server/settings/creator_plugins.py new file mode 100644 index 0000000000..fee01bad26 --- /dev/null +++ b/server_addon/aftereffects/server/settings/creator_plugins.py @@ -0,0 +1,16 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class CreateRenderPlugin(BaseSettingsModel): + mark_for_review: bool = Field(True, title="Review") + defaults: list[str] = Field(default_factory=list, + title="Default Variants") + + +class AfterEffectsCreatorPlugins(BaseSettingsModel): + RenderCreator: CreateRenderPlugin = Field( + title="Create Render", + default_factory=CreateRenderPlugin, + ) diff --git a/server_addon/aftereffects/server/settings/imageio.py b/server_addon/aftereffects/server/settings/imageio.py new file mode 100644 index 0000000000..55160ffd11 --- /dev/null +++ b/server_addon/aftereffects/server/settings/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class AfterEffectsImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/aftereffects/server/settings/main.py b/server_addon/aftereffects/server/settings/main.py new file mode 100644 index 0000000000..9da872bd92 --- /dev/null +++ b/server_addon/aftereffects/server/settings/main.py @@ -0,0 +1,62 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + +from .imageio import AfterEffectsImageIOModel +from .creator_plugins import AfterEffectsCreatorPlugins +from .publish_plugins import AfterEffectsPublishPlugins +from .workfile_builder import WorkfileBuilderPlugin + + +class AfterEffectsSettings(BaseSettingsModel): + """AfterEffects Project Settings.""" + + imageio: AfterEffectsImageIOModel = Field( + default_factory=AfterEffectsImageIOModel, + title="OCIO config" + ) + create: AfterEffectsCreatorPlugins = Field( + default_factory=AfterEffectsCreatorPlugins, + title="Creator plugins" + ) + + publish: AfterEffectsPublishPlugins = Field( + default_factory=AfterEffectsPublishPlugins, + title="Publish plugins" + ) + + workfile_builder: WorkfileBuilderPlugin = Field( + default_factory=WorkfileBuilderPlugin, + title="Workfile Builder" + ) + + +DEFAULT_AFTEREFFECTS_SETTING = { + "create": { + "RenderCreator": { + "mark_for_review": True, + "defaults": [ + "Main" + ] + } + }, + "publish": { + "CollectReview": { + "enabled": True + }, + "ValidateSceneSettings": { + "enabled": True, + "optional": True, + "active": True, + "skip_resolution_check": [ + ".*" + ], + "skip_timelines_check": [ + ".*" + ] + } + }, + "workfile_builder": { + "create_first_version": False, + "custom_templates": [] + } +} diff --git a/server_addon/aftereffects/server/settings/publish_plugins.py b/server_addon/aftereffects/server/settings/publish_plugins.py new file mode 100644 index 0000000000..0d90b08b5a --- /dev/null +++ b/server_addon/aftereffects/server/settings/publish_plugins.py @@ -0,0 +1,36 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class CollectReviewPluginModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + + +class ValidateSceneSettingsPlugin(BaseSettingsModel): + """Validate naming of products and layers""" # + _isGroup = True + enabled: bool = True + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + + skip_resolution_check: list[str] = Field( + default_factory=list, + title="Skip Resolution Check for Tasks" + ) + + skip_timelines_check: list[str] = Field( + default_factory=list, + title="Skip Timeline Check for Tasks" + ) + + +class AfterEffectsPublishPlugins(BaseSettingsModel): + CollectReview: CollectReviewPluginModel = Field( + default_facotory=CollectReviewPluginModel, + title="Collect Review" + ) + ValidateSceneSettings: ValidateSceneSettingsPlugin = Field( + title="Validate Scene Settings", + default_factory=ValidateSceneSettingsPlugin, + ) diff --git a/server_addon/aftereffects/server/settings/workfile_builder.py b/server_addon/aftereffects/server/settings/workfile_builder.py new file mode 100644 index 0000000000..d9d5fa41bf --- /dev/null +++ b/server_addon/aftereffects/server/settings/workfile_builder.py @@ -0,0 +1,25 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel + + +class CustomBuilderTemplate(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + ) + template_path: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel + ) + + +class WorkfileBuilderPlugin(BaseSettingsModel): + _title = "Workfile Builder" + create_first_version: bool = Field( + False, + title="Create first workfile" + ) + + custom_templates: list[CustomBuilderTemplate] = Field( + default_factory=CustomBuilderTemplate + ) diff --git a/server_addon/aftereffects/server/version.py b/server_addon/aftereffects/server/version.py new file mode 100644 index 0000000000..d4b9e2d7f3 --- /dev/null +++ b/server_addon/aftereffects/server/version.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +"""Package declaring addon version.""" +__version__ = "0.1.0" diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py new file mode 100644 index 0000000000..a3fd92eb6e --- /dev/null +++ b/server_addon/applications/server/__init__.py @@ -0,0 +1,153 @@ +import os +import json +import copy + +from ayon_server.addons import BaseServerAddon +from ayon_server.lib.postgres import Postgres + +from .version import __version__ +from .settings import ApplicationsAddonSettings, DEFAULT_VALUES + + +def get_enum_items_from_groups(groups): + label_by_name = {} + for group in groups: + group_name = group["name"] + group_label = group["label"] or group_name + for variant in group["variants"]: + variant_name = variant["name"] + if not variant_name: + continue + variant_label = variant["label"] or variant_name + full_name = f"{group_name}/{variant_name}" + full_label = f"{group_label} {variant_label}" + label_by_name[full_name] = full_label + enum_items = [] + for full_name in sorted(label_by_name): + enum_items.append( + {"value": full_name, "label": label_by_name[full_name]} + ) + return enum_items + + +class ApplicationsAddon(BaseServerAddon): + name = "applications" + version = __version__ + settings_model = ApplicationsAddonSettings + + async def get_default_settings(self): + applications_path = os.path.join(self.addon_dir, "applications.json") + tools_path = os.path.join(self.addon_dir, "tools.json") + default_values = copy.deepcopy(DEFAULT_VALUES) + with open(applications_path, "r") as stream: + default_values.update(json.load(stream)) + + with open(tools_path, "r") as stream: + default_values.update(json.load(stream)) + + return self.get_settings_model()(**default_values) + + async def setup(self): + need_restart = await self.create_applications_attribute() + if need_restart: + self.request_server_restart() + + async def create_applications_attribute(self) -> bool: + """Make sure there are required attributes which ftrack addon needs. + + Returns: + bool: 'True' if an attribute was created or updated. + """ + + settings_model = await self.get_studio_settings() + studio_settings = settings_model.dict() + applications = studio_settings["applications"] + _applications = applications.pop("additional_apps") + for name, value in applications.items(): + value["name"] = name + _applications.append(value) + + query = "SELECT name, position, scope, data from public.attributes" + + apps_attrib_name = "applications" + tools_attrib_name = "tools" + + apps_enum = get_enum_items_from_groups(_applications) + tools_enum = get_enum_items_from_groups(studio_settings["tool_groups"]) + apps_attribute_data = { + "type": "list_of_strings", + "title": "Applications", + "enum": apps_enum + } + tools_attribute_data = { + "type": "list_of_strings", + "title": "Tools", + "enum": tools_enum + } + apps_scope = ["project"] + tools_scope = ["project", "folder", "task"] + + apps_match_position = None + apps_matches = False + tools_match_position = None + tools_matches = False + position = 1 + async for row in Postgres.iterate(query): + position += 1 + if row["name"] == apps_attrib_name: + # Check if scope is matching ftrack addon requirements + if ( + set(row["scope"]) == set(apps_scope) + and row["data"].get("enum") == apps_enum + ): + apps_matches = True + apps_match_position = row["position"] + + elif row["name"] == tools_attrib_name: + if ( + set(row["scope"]) == set(tools_scope) + and row["data"].get("enum") == tools_enum + ): + tools_matches = True + tools_match_position = row["position"] + + if apps_matches and tools_matches: + return False + + postgre_query = "\n".join(( + "INSERT INTO public.attributes", + " (name, position, scope, data)", + "VALUES", + " ($1, $2, $3, $4)", + "ON CONFLICT (name)", + "DO UPDATE SET", + " scope = $3,", + " data = $4", + )) + if not apps_matches: + # Reuse position from found attribute + if apps_match_position is None: + apps_match_position = position + position += 1 + + await Postgres.execute( + postgre_query, + apps_attrib_name, + apps_match_position, + apps_scope, + apps_attribute_data, + ) + + if not tools_matches: + if tools_match_position is None: + tools_match_position = position + position += 1 + + await Postgres.execute( + postgre_query, + tools_attrib_name, + tools_match_position, + tools_scope, + tools_attribute_data, + ) + return True diff --git a/server_addon/applications/server/applications.json b/server_addon/applications/server/applications.json new file mode 100644 index 0000000000..b19308ee7c --- /dev/null +++ b/server_addon/applications/server/applications.json @@ -0,0 +1,1125 @@ +{ + "applications": { + "maya": { + "enabled": true, + "label": "Maya", + "icon": "{}/app_icons/maya.png", + "host_name": "maya", + "environment": "{\n \"MAYA_DISABLE_CLIC_IPM\": \"Yes\",\n \"MAYA_DISABLE_CIP\": \"Yes\",\n \"MAYA_DISABLE_CER\": \"Yes\",\n \"PYMEL_SKIP_MEL_INIT\": \"Yes\",\n \"LC_ALL\": \"C\"\n}\n", + "variants": [ + { + "name": "2023", + "label": "2023", + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\Maya2023\\bin\\maya.exe" + ], + "darwin": [], + "linux": [ + "/usr/autodesk/maya2023/bin/maya" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MAYA_VERSION\": \"2023\"\n}", + "use_python_2": false + }, + { + "name": "2022", + "label": "2022", + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\Maya2022\\bin\\maya.exe" + ], + "darwin": [], + "linux": [ + "/usr/autodesk/maya2022/bin/maya" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MAYA_VERSION\": \"2022\"\n}", + "use_python_2": false + }, + { + "name": "2020", + "label": "2020", + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe" + ], + "darwin": [], + "linux": [ + "/usr/autodesk/maya2020/bin/maya" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MAYA_VERSION\": \"2020\"\n}", + "use_python_2": true + }, + { + "name": "2019", + "label": "2019", + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\Maya2019\\bin\\maya.exe" + ], + "darwin": [], + "linux": [ + "/usr/autodesk/maya2019/bin/maya" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MAYA_VERSION\": \"2019\"\n}", + "use_python_2": true + }, + { + "name": "2018", + "label": "2018", + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\Maya2018\\bin\\maya.exe" + ], + "darwin": [], + "linux": [ + "/usr/autodesk/maya2018/bin/maya" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MAYA_VERSION\": \"2018\"\n}", + "use_python_2": true + } + ] + }, + "adsk_3dsmax": { + "enabled": true, + "label": "3ds Max", + "icon": "{}/app_icons/3dsmax.png", + "host_name": "max", + "environment": "{\n \"ADSK_3DSMAX_STARTUPSCRIPTS_ADDON_DIR\": \"{OPENPYPE_ROOT}/openpype/hosts/max/startup\"\n}", + "variants": [ + { + "name": "2023", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Autodesk\\3ds Max 2023\\3dsmax.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "-U MAXScript {OPENPYPE_ROOT}\\openpype\\hosts\\max\\startup\\startup.ms" + ], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"3DSMAX_VERSION\": \"2023\"\n}" + } + ] + }, + "flame": { + "enabled": true, + "label": "Flame", + "icon": "{}/app_icons/flame.png", + "host_name": "flame", + "environment": "{\n \"FLAME_SCRIPT_DIRS\": {\n \"windows\": \"\",\n \"darwin\": \"\",\n \"linux\": \"\"\n },\n \"FLAME_WIRETAP_HOSTNAME\": \"\",\n \"FLAME_WIRETAP_VOLUME\": \"stonefs\",\n \"FLAME_WIRETAP_GROUP\": \"staff\"\n}", + "variants": [ + { + "name": "2021", + "label": "2021", + "executables": { + "windows": [], + "darwin": [ + "/opt/Autodesk/flame_2021/bin/flame.app/Contents/MacOS/startApp" + ], + "linux": [ + "/opt/Autodesk/flame_2021/bin/startApplication" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"OPENPYPE_FLAME_PYTHON_EXEC\": \"/opt/Autodesk/python/2021/bin/python2.7\",\n \"OPENPYPE_FLAME_PYTHONPATH\": \"/opt/Autodesk/flame_2021/python\",\n \"OPENPYPE_WIRETAP_TOOLS\": \"/opt/Autodesk/wiretap/tools/2021\"\n}", + "use_python_2": true + }, + { + "name": "2021_1", + "label": "2021.1", + "executables": { + "windows": [], + "darwin": [ + "/opt/Autodesk/flame_2021.1/bin/flame.app/Contents/MacOS/startApp" + ], + "linux": [ + "/opt/Autodesk/flame_2021.1/bin/startApplication" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"OPENPYPE_FLAME_PYTHON_EXEC\": \"/opt/Autodesk/python/2021.1/bin/python2.7\",\n \"OPENPYPE_FLAME_PYTHONPATH\": \"/opt/Autodesk/flame_2021.1/python\",\n \"OPENPYPE_WIRETAP_TOOLS\": \"/opt/Autodesk/wiretap/tools/2021.1\"\n}", + "use_python_2": true + } + ] + }, + "nuke": { + "enabled": true, + "label": "Nuke", + "icon": "{}/app_icons/nuke.png", + "host_name": "nuke", + "environment": "{\n \"NUKE_PATH\": [\n \"{NUKE_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/nuke\"\n ]\n}", + "variants": [ + { + "name": "14-0", + "label": "14.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v4/Nuke14.0v4.app" + ], + "linux": [ + "/usr/local/Nuke14.0v4/Nuke14.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-2", + "label": "13.2", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v5\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v5/Nuke13.2v5.app" + ], + "linux": [ + "/usr/local/Nuke13.2v5/Nuke13.2" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-0", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [ + "/Applications/Nuke13.0v1/Nuke13.0v1.app" + ], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "nukeassist": { + "enabled": true, + "label": "Nuke Assist", + "icon": "{}/app_icons/nuke.png", + "host_name": "nuke", + "environment": "{\n \"NUKE_PATH\": [\n \"{NUKE_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/nuke\"\n ]\n}", + "variants": [ + { + "name": "14-0", + "label": "14.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v4/NukeAssist14.0v4.app" + ], + "linux": [ + "/usr/local/Nuke14.0v4/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [], + "linux": [ + "--nukeassist" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-2", + "label": "13.2", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v5\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v5/NukeAssist13.2v5.app" + ], + "linux": [ + "/usr/local/Nuke13.2v5/Nuke13.2" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [], + "linux": [ + "--nukeassist" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-0", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [ + "/Applications/Nuke13.0v1/NukeAssist13.0v1.app" + ], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--nukeassist" + ], + "darwin": [], + "linux": [ + "--nukeassist" + ] + }, + "environment": "{}" + } + ] + }, + "nukex": { + "enabled": true, + "label": "Nuke X", + "icon": "{}/app_icons/nukex.png", + "host_name": "nuke", + "environment": "{\n \"NUKE_PATH\": [\n \"{NUKE_PATH}\",\n \"{OPENPYPE_STUDIO_PLUGINS}/nuke\"\n ]\n}", + "variants": [ + { + "name": "14-0", + "label": "14.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v4/NukeX14.0v4.app" + ], + "linux": [ + "/usr/local/Nuke14.0v4/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [], + "linux": [ + "--nukex" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-2", + "label": "13.2", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v5\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v5/NukeX13.2v5.app" + ], + "linux": [ + "/usr/local/Nuke13.2v5/Nuke13.2" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [], + "linux": [ + "--nukex" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-0", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [ + "/Applications/Nuke13.0v1/NukeX13.0v1.app" + ], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--nukex" + ], + "darwin": [], + "linux": [ + "--nukex" + ] + }, + "environment": "{}" + } + ] + }, + "nukestudio": { + "enabled": true, + "label": "Nuke Studio", + "icon": "{}/app_icons/nukestudio.png", + "host_name": "hiero", + "environment": "{\n \"WORKFILES_STARTUP\": \"0\",\n \"TAG_ASSETBUILD_STARTUP\": \"0\"\n}", + "variants": [ + { + "name": "14-0", + "label": "14.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v4/NukeStudio14.0v4.app" + ], + "linux": [ + "/usr/local/Nuke14.0v4/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [], + "linux": [ + "--studio" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-2", + "label": "13.2", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v5\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v5/NukeStudio13.2v5.app" + ], + "linux": [ + "/usr/local/Nuke13.2v5/Nuke13.2" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [], + "linux": [ + "--studio" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-0", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [ + "/Applications/Nuke13.0v1/NukeStudio13.0v1.app" + ], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--studio" + ], + "darwin": [], + "linux": [ + "--studio" + ] + }, + "environment": "{}" + } + ] + }, + "hiero": { + "enabled": true, + "label": "Hiero", + "icon": "{}/app_icons/hiero.png", + "host_name": "hiero", + "environment": "{\n \"WORKFILES_STARTUP\": \"0\",\n \"TAG_ASSETBUILD_STARTUP\": \"0\"\n}", + "variants": [ + { + "name": "14-0", + "label": "14.0", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke14.0v4\\Nuke14.0.exe" + ], + "darwin": [ + "/Applications/Nuke14.0v4/Hiero14.0v4.app" + ], + "linux": [ + "/usr/local/Nuke14.0v4/Nuke14.0" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [], + "linux": [ + "--hiero" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-2", + "label": "13.2", + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v5\\Nuke13.2.exe" + ], + "darwin": [ + "/Applications/Nuke13.2v5/Hiero13.2v5.app" + ], + "linux": [ + "/usr/local/Nuke13.2v5/Nuke13.2" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [], + "linux": [ + "--hiero" + ] + }, + "environment": "{}", + "use_python_2": false + }, + { + "name": "13-0", + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [ + "/Applications/Nuke13.0v1/Hiero13.0v1.app" + ], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": [ + "--hiero" + ], + "darwin": [], + "linux": [ + "--hiero" + ] + }, + "environment": "{}" + } + ] + }, + "fusion": { + "enabled": true, + "label": "Fusion", + "icon": "{}/app_icons/fusion.png", + "host_name": "fusion", + "environment": "{\n \"FUSION_PYTHON3_HOME\": {\n \"windows\": \"{LOCALAPPDATA}/Programs/Python/Python36\",\n \"darwin\": \"~/Library/Python/3.6/bin\",\n \"linux\": \"/opt/Python/3.6/bin\"\n }\n}", + "variants": [ + { + "name": "17", + "label": "17", + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 17\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "16", + "label": "16", + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 16\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "9", + "label": "9", + "executables": { + "windows": [ + "C:\\Program Files\\Blackmagic Design\\Fusion 9\\Fusion.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "resolve": { + "enabled": true, + "label": "Resolve", + "icon": "{}/app_icons/resolve.png", + "host_name": "resolve", + "environment": "{\n \"RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR\": [],\n \"RESOLVE_PYTHON3_HOME\": {\n \"windows\": \"{LOCALAPPDATA}/Programs/Python/Python36\",\n \"darwin\": \"~/Library/Python/3.6/bin\",\n \"linux\": \"/opt/Python/3.6/bin\"\n }\n}", + "variants": [ + { + "name": "stable", + "label": "stable", + "executables": { + "windows": [ + "C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "houdini": { + "enabled": true, + "label": "Houdini", + "icon": "{}/app_icons/houdini.png", + "host_name": "houdini", + "environment": "{}", + "variants": [ + { + "name": "18-5", + "label": "18.5", + "executables": { + "windows": [ + "C:\\Program Files\\Side Effects Software\\Houdini 18.5.499\\bin\\houdini.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": true + }, + { + "name": "18", + "label": "18", + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": true + }, + { + "name": "17", + "label": "17", + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}", + "use_python_2": true + } + ] + }, + "blender": { + "enabled": true, + "label": "Blender", + "icon": "{}/app_icons/blender.png", + "host_name": "blender", + "environment": "{}", + "variants": [ + { + "name": "2-83", + "label": "2.83", + "executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--python-use-system-env" + ], + "darwin": [ + "--python-use-system-env" + ], + "linux": [ + "--python-use-system-env" + ] + }, + "environment": "{}" + }, + { + "name": "2-90", + "label": "2.90", + "executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--python-use-system-env" + ], + "darwin": [ + "--python-use-system-env" + ], + "linux": [ + "--python-use-system-env" + ] + }, + "environment": "{}" + }, + { + "name": "2-91", + "label": "2.91", + "executables": { + "windows": [ + "C:\\Program Files\\Blender Foundation\\Blender 2.91\\blender.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [ + "--python-use-system-env" + ], + "darwin": [ + "--python-use-system-env" + ], + "linux": [ + "--python-use-system-env" + ] + }, + "environment": "{}" + } + ] + }, + "harmony": { + "enabled": true, + "label": "Harmony", + "icon": "{}/app_icons/harmony.png", + "host_name": "harmony", + "environment": "{\n \"AVALON_HARMONY_WORKFILES_ON_LAUNCH\": \"1\"\n}", + "variants": [ + { + "name": "21", + "label": "21", + "executables": { + "windows": [ + "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 21 Premium\\win64\\bin\\HarmonyPremium.exe" + ], + "darwin": [ + "/Applications/Toon Boom Harmony 21 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "20", + "label": "20", + "executables": { + "windows": [ + "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 20 Premium\\win64\\bin\\HarmonyPremium.exe" + ], + "darwin": [ + "/Applications/Toon Boom Harmony 20 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "17", + "label": "17", + "executables": { + "windows": [ + "c:\\Program Files (x86)\\Toon Boom Animation\\Toon Boom Harmony 17 Premium\\win64\\bin\\HarmonyPremium.exe" + ], + "darwin": [ + "/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" + ], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "tvpaint": { + "enabled": true, + "label": "TVPaint", + "icon": "{}/app_icons/tvpaint.png", + "host_name": "tvpaint", + "environment": "{}", + "variants": [ + { + "name": "animation_11-64bits", + "label": "11 (64bits)", + "executables": { + "windows": [ + "C:\\Program Files\\TVPaint Developpement\\TVPaint Animation 11 (64bits)\\TVPaint Animation 11 (64bits).exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "animation_11-32bits", + "label": "11 (32bits)", + "executables": { + "windows": [ + "C:\\Program Files (x86)\\TVPaint Developpement\\TVPaint Animation 11 (32bits)\\TVPaint Animation 11 (32bits).exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "photoshop": { + "enabled": true, + "label": "Photoshop", + "icon": "{}/app_icons/photoshop.png", + "host_name": "photoshop", + "environment": "{\n \"AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH\": \"1\",\n \"WORKFILES_SAVE_AS\": \"Yes\"\n}", + "variants": [ + { + "name": "2020", + "label": "2020", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "2021", + "label": "2021", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "2022", + "label": "2022", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe Photoshop 2022\\Photoshop.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "aftereffects": { + "enabled": true, + "label": "AfterEffects", + "icon": "{}/app_icons/aftereffects.png", + "host_name": "aftereffects", + "environment": "{\n \"AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH\": \"1\",\n \"WORKFILES_SAVE_AS\": \"Yes\"\n}", + "variants": [ + { + "name": "2020", + "label": "2020", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2020\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "2021", + "label": "2021", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + }, + { + "name": "2022", + "label": "2022", + "executables": { + "windows": [ + "C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\AfterFX.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{\n \"MULTIPROCESS\": \"No\"\n}" + } + ] + }, + "celaction": { + "enabled": true, + "label": "CelAction 2D", + "icon": "app_icons/celaction.png", + "host_name": "celaction", + "environment": "{\n \"CELACTION_TEMPLATE\": \"{OPENPYPE_REPOS_ROOT}/openpype/hosts/celaction/celaction_template_scene.scn\"\n}", + "variants": [ + { + "name": "local", + "label": "local", + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "unreal": { + "enabled": true, + "label": "Unreal Editor", + "icon": "{}/app_icons/ue4.png", + "host_name": "unreal", + "environment": "{}", + "variants": [ + { + "name": "4-26", + "label": "4.26", + "executables": {}, + "arguments": {}, + "environment": "{}" + } + ] + }, + "djvview": { + "enabled": true, + "label": "DJV View", + "icon": "{}/app_icons/djvView.png", + "host_name": "", + "environment": "{}", + "variants": [ + { + "name": "1-1", + "label": "1.1", + "executables": { + "windows": [], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": [], + "darwin": [], + "linux": [] + }, + "environment": "{}" + } + ] + }, + "additional_apps": [] + } +} diff --git a/server_addon/applications/server/settings.py b/server_addon/applications/server/settings.py new file mode 100644 index 0000000000..fd481b6ce8 --- /dev/null +++ b/server_addon/applications/server/settings.py @@ -0,0 +1,201 @@ +import json +from pydantic import Field, validator + +from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from ayon_server.exceptions import BadRequestException + + +def validate_json_dict(value): + if not value.strip(): + return "{}" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, dict) + except json.JSONDecodeError as exc: + print(exc) + success = False + + if not success: + raise BadRequestException( + "Environment's can't be parsed as json object" + ) + return value + + +class MultiplatformStrList(BaseSettingsModel): + windows: list[str] = Field(default_factory=list, title="Windows") + linux: list[str] = Field(default_factory=list, title="Linux") + darwin: list[str] = Field(default_factory=list, title="MacOS") + + +class AppVariant(BaseSettingsModel): + name: str = Field("", title="Name") + label: str = Field("", title="Label") + executables: MultiplatformStrList = Field( + default_factory=MultiplatformStrList, title="Executables" + ) + arguments: MultiplatformStrList = Field( + default_factory=MultiplatformStrList, title="Arguments" + ) + environment: str = Field("{}", title="Environment", widget="textarea") + + @validator("environment") + def validate_json(cls, value): + return validate_json_dict(value) + + +class AppVariantWithPython(AppVariant): + use_python_2: bool = Field(False, title="Use Python 2") + + +class AppGroup(BaseSettingsModel): + enabled: bool = Field(True) + label: str = Field("", title="Label") + host_name: str = Field("", title="Host name") + icon: str = Field("", title="Icon") + environment: str = Field("{}", title="Environment", widget="textarea") + + variants: list[AppVariant] = Field( + default_factory=list, + title="Variants", + description="Different variants of the applications", + section="Variants", + ) + + @validator("variants") + def validate_unique_name(cls, value): + ensure_unique_names(value) + return value + + +class AppGroupWithPython(AppGroup): + variants: list[AppVariantWithPython] = Field( + default_factory=list, + title="Variants", + description="Different variants of the applications", + section="Variants", + ) + + +class AdditionalAppGroup(BaseSettingsModel): + enabled: bool = Field(True) + name: str = Field("", title="Name") + label: str = Field("", title="Label") + host_name: str = Field("", title="Host name") + icon: str = Field("", title="Icon") + environment: str = Field("{}", title="Environment", widget="textarea") + + variants: list[AppVariantWithPython] = Field( + default_factory=list, + title="Variants", + description="Different variants of the applications", + section="Variants", + ) + + @validator("variants") + def validate_unique_name(cls, value): + ensure_unique_names(value) + return value + + +class ToolVariantModel(BaseSettingsModel): + name: str = Field("", title="Name") + label: str = Field("", title="Label") + host_names: list[str] = Field(default_factory=list, title="Hosts") + # TODO use applications enum if possible + app_variants: list[str] = Field(default_factory=list, title="Applications") + environment: str = Field("{}", title="Environments", widget="textarea") + + @validator("environment") + def validate_json(cls, value): + return validate_json_dict(value) + + +class ToolGroupModel(BaseSettingsModel): + name: str = Field("", title="Name") + label: str = Field("", title="Label") + environment: str = Field("{}", title="Environments", widget="textarea") + variants: list[ToolVariantModel] = Field( + default_factory=ToolVariantModel + ) + + @validator("environment") + def validate_json(cls, value): + return validate_json_dict(value) + + @validator("variants") + def validate_unique_name(cls, value): + ensure_unique_names(value) + return value + + +class ApplicationsSettings(BaseSettingsModel): + """Applications settings""" + + maya: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Autodesk Maya") + adsk_3dsmax: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Autodesk 3ds Max") + flame: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Autodesk Flame") + nuke: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Nuke") + nukeassist: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Nuke Assist") + nukex: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Nuke X") + nukestudio: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Nuke Studio") + hiero: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Hiero") + fusion: AppGroup = Field( + default_factory=AppGroupWithPython, title="Fusion") + resolve: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Resolve") + houdini: AppGroupWithPython = Field( + default_factory=AppGroupWithPython, title="Houdini") + blender: AppGroup = Field( + default_factory=AppGroupWithPython, title="Blender") + harmony: AppGroup = Field( + default_factory=AppGroupWithPython, title="Harmony") + tvpaint: AppGroup = Field( + default_factory=AppGroupWithPython, title="TVPaint") + photoshop: AppGroup = Field( + default_factory=AppGroupWithPython, title="Adobe Photoshop") + aftereffects: AppGroup = Field( + default_factory=AppGroupWithPython, title="Adobe After Effects") + celaction: AppGroup = Field( + default_factory=AppGroupWithPython, title="Celaction 2D") + unreal: AppGroup = Field( + default_factory=AppGroupWithPython, title="Unreal Editor") + additional_apps: list[AdditionalAppGroup] = Field( + default_factory=list, title="Additional Applications") + + @validator("additional_apps") + def validate_unique_name(cls, value): + ensure_unique_names(value) + return value + + +class ApplicationsAddonSettings(BaseSettingsModel): + applications: ApplicationsSettings = Field( + default_factory=ApplicationsSettings, + title="Applications", + scope=["studio"] + ) + tool_groups: list[ToolGroupModel] = Field( + default_factory=list, + scope=["studio"] + ) + only_available: bool = Field( + True, title="Show only available applications") + + @validator("tool_groups") + def validate_unique_name(cls, value): + ensure_unique_names(value) + return value + + +DEFAULT_VALUES = { + "only_available": False +} diff --git a/server_addon/applications/server/tools.json b/server_addon/applications/server/tools.json new file mode 100644 index 0000000000..54bee11cf7 --- /dev/null +++ b/server_addon/applications/server/tools.json @@ -0,0 +1,55 @@ +{ + "tool_groups": [ + { + "environment": "{\n \"MTOA\": \"{STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}\",\n \"MAYA_RENDER_DESC_PATH\": \"{MTOA}\",\n \"MAYA_MODULE_PATH\": \"{MTOA}\",\n \"ARNOLD_PLUGIN_PATH\": \"{MTOA}/shaders\",\n \"MTOA_EXTENSIONS_PATH\": {\n \"darwin\": \"{MTOA}/extensions\",\n \"linux\": \"{MTOA}/extensions\",\n \"windows\": \"{MTOA}/extensions\"\n },\n \"MTOA_EXTENSIONS\": {\n \"darwin\": \"{MTOA}/extensions\",\n \"linux\": \"{MTOA}/extensions\",\n \"windows\": \"{MTOA}/extensions\"\n },\n \"DYLD_LIBRARY_PATH\": {\n \"darwin\": \"{MTOA}/bin\"\n },\n \"PATH\": {\n \"windows\": \"{PATH};{MTOA}/bin\"\n }\n}", + "name": "mtoa", + "label": "Autodesk Arnold", + "variants": [ + { + "host_names": [], + "app_variants": [], + "environment": "{\n \"MTOA_VERSION\": \"3.2\"\n}", + "name": "3-2", + "label": "3.2" + }, + { + "host_names": [], + "app_variants": [], + "environment": "{\n \"MTOA_VERSION\": \"3.1\"\n}", + "name": "3-1", + "label": "3.1" + } + ] + }, + { + "environment": "{}", + "name": "vray", + "label": "Chaos Group Vray", + "variants": [] + }, + { + "environment": "{}", + "name": "yeti", + "label": "Peregrine Labs Yeti", + "variants": [] + }, + { + "environment": "{}", + "name": "renderman", + "label": "Pixar Renderman", + "variants": [ + { + "host_names": [ + "maya" + ], + "app_variants": [ + "maya/2022" + ], + "environment": "{\n \"RFMTREE\": {\n \"windows\": \"C:\\\\Program Files\\\\Pixar\\\\RenderManForMaya-24.3\",\n \"darwin\": \"/Applications/Pixar/RenderManForMaya-24.3\",\n \"linux\": \"/opt/pixar/RenderManForMaya-24.3\"\n },\n \"RMANTREE\": {\n \"windows\": \"C:\\\\Program Files\\\\Pixar\\\\RenderManProServer-24.3\",\n \"darwin\": \"/Applications/Pixar/RenderManProServer-24.3\",\n \"linux\": \"/opt/pixar/RenderManProServer-24.3\"\n }\n}", + "name": "24-3-maya", + "label": "24.3 RFM" + } + ] + } + ] +} diff --git a/server_addon/applications/server/version.py b/server_addon/applications/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/applications/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/blender/server/__init__.py b/server_addon/blender/server/__init__.py new file mode 100644 index 0000000000..a7d6cb4400 --- /dev/null +++ b/server_addon/blender/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import BlenderSettings, DEFAULT_VALUES + + +class BlenderAddon(BaseServerAddon): + name = "blender" + title = "Blender" + version = __version__ + settings_model: Type[BlenderSettings] = BlenderSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/blender/server/settings/__init__.py b/server_addon/blender/server/settings/__init__.py new file mode 100644 index 0000000000..3d51e5c3e1 --- /dev/null +++ b/server_addon/blender/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + BlenderSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "BlenderSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/blender/server/settings/imageio.py b/server_addon/blender/server/settings/imageio.py new file mode 100644 index 0000000000..a6d3c5ff64 --- /dev/null +++ b/server_addon/blender/server/settings/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class BlenderImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/blender/server/settings/main.py b/server_addon/blender/server/settings/main.py new file mode 100644 index 0000000000..ec969afa93 --- /dev/null +++ b/server_addon/blender/server/settings/main.py @@ -0,0 +1,53 @@ +from pydantic import Field +from ayon_server.settings import ( + BaseSettingsModel, + TemplateWorkfileBaseOptions, +) + +from .imageio import BlenderImageIOModel +from .publish_plugins import ( + PublishPuginsModel, + DEFAULT_BLENDER_PUBLISH_SETTINGS +) + + +class UnitScaleSettingsModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + apply_on_opening: bool = Field( + False, title="Apply on Opening Existing Files") + base_file_unit_scale: float = Field( + 1.0, title="Base File Unit Scale" + ) + + +class BlenderSettings(BaseSettingsModel): + unit_scale_settings: UnitScaleSettingsModel = Field( + default_factory=UnitScaleSettingsModel, + title="Set Unit Scale" + ) + imageio: BlenderImageIOModel = Field( + default_factory=BlenderImageIOModel, + title="Color Management (ImageIO)" + ) + workfile_builder: TemplateWorkfileBaseOptions = Field( + default_factory=TemplateWorkfileBaseOptions, + title="Workfile Builder" + ) + publish: PublishPuginsModel = Field( + default_factory=PublishPuginsModel, + title="Publish Plugins" + ) + + +DEFAULT_VALUES = { + "unit_scale_settings": { + "enabled": True, + "apply_on_opening": False, + "base_file_unit_scale": 0.01 + }, + "publish": DEFAULT_BLENDER_PUBLISH_SETTINGS, + "workfile_builder": { + "create_first_version": False, + "custom_templates": [] + } +} diff --git a/server_addon/blender/server/settings/publish_plugins.py b/server_addon/blender/server/settings/publish_plugins.py new file mode 100644 index 0000000000..43ed3e3d0d --- /dev/null +++ b/server_addon/blender/server/settings/publish_plugins.py @@ -0,0 +1,273 @@ +import json +from pydantic import Field, validator +from ayon_server.exceptions import BadRequestException +from ayon_server.settings import BaseSettingsModel + + +def validate_json_dict(value): + if not value.strip(): + return "{}" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, dict) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "Environment's can't be parsed as json object" + ) + return value + + +class ValidatePluginModel(BaseSettingsModel): + enabled: bool = Field(True) + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + + +class ExtractBlendModel(BaseSettingsModel): + enabled: bool = Field(True) + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + families: list[str] = Field( + default_factory=list, + title="Families" + ) + + +class ExtractPlayblastModel(BaseSettingsModel): + enabled: bool = Field(True) + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + presets: str = Field("", title="Presets", widget="textarea") + + @validator("presets") + def validate_json(cls, value): + return validate_json_dict(value) + + +class PublishPuginsModel(BaseSettingsModel): + ValidateCameraZeroKeyframe: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Camera Zero Keyframe", + section="Validators" + ) + ValidateMeshHasUvs: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Mesh Has Uvs" + ) + ValidateMeshNoNegativeScale: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Mesh No Negative Scale" + ) + ValidateTransformZero: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Transform Zero" + ) + ValidateNoColonsInName: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate No Colons In Name" + ) + ExtractBlend: ExtractBlendModel = Field( + default_factory=ExtractBlendModel, + title="Extract Blend", + section="Extractors" + ) + ExtractFBX: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Extract FBX" + ) + ExtractABC: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Extract ABC" + ) + ExtractBlendAnimation: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Extract Blend Animation" + ) + ExtractAnimationFBX: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Extract Animation FBX" + ) + ExtractCamera: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Extract Camera" + ) + ExtractLayout: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Extract Layout" + ) + ExtractThumbnail: ExtractPlayblastModel = Field( + default_factory=ExtractPlayblastModel, + title="Extract Thumbnail" + ) + ExtractPlayblast: ExtractPlayblastModel = Field( + default_factory=ExtractPlayblastModel, + title="Extract Playblast" + ) + + +DEFAULT_BLENDER_PUBLISH_SETTINGS = { + "ValidateCameraZeroKeyframe": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshHasUvs": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshNoNegativeScale": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateTransformZero": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateNoColonsInName": { + "enabled": True, + "optional": False, + "active": True + }, + "ExtractBlend": { + "enabled": True, + "optional": True, + "active": True, + "families": [ + "model", + "camera", + "rig", + "action", + "layout" + ] + }, + "ExtractFBX": { + "enabled": True, + "optional": True, + "active": False + }, + "ExtractABC": { + "enabled": True, + "optional": True, + "active": False + }, + "ExtractBlendAnimation": { + "enabled": True, + "optional": True, + "active": True + }, + "ExtractAnimationFBX": { + "enabled": True, + "optional": True, + "active": False + }, + "ExtractCamera": { + "enabled": True, + "optional": True, + "active": True + }, + "ExtractLayout": { + "enabled": True, + "optional": True, + "active": False + }, + "ExtractThumbnail": { + "enabled": True, + "optional": True, + "active": True, + "presets": json.dumps( + { + "model": { + "image_settings": { + "file_format": "JPEG", + "color_mode": "RGB", + "quality": 100 + }, + "display_options": { + "shading": { + "light": "STUDIO", + "studio_light": "Default", + "type": "SOLID", + "color_type": "OBJECT", + "show_xray": False, + "show_shadows": False, + "show_cavity": True + }, + "overlay": { + "show_overlays": False + } + } + }, + "rig": { + "image_settings": { + "file_format": "JPEG", + "color_mode": "RGB", + "quality": 100 + }, + "display_options": { + "shading": { + "light": "STUDIO", + "studio_light": "Default", + "type": "SOLID", + "color_type": "OBJECT", + "show_xray": True, + "show_shadows": False, + "show_cavity": False + }, + "overlay": { + "show_overlays": True, + "show_ortho_grid": False, + "show_floor": False, + "show_axis_x": False, + "show_axis_y": False, + "show_axis_z": False, + "show_text": False, + "show_stats": False, + "show_cursor": False, + "show_annotation": False, + "show_extras": False, + "show_relationship_lines": False, + "show_outline_selected": False, + "show_motion_paths": False, + "show_object_origins": False, + "show_bones": True + } + } + } + }, + indent=4, + ) + }, + "ExtractPlayblast": { + "enabled": True, + "optional": True, + "active": True, + "presets": json.dumps( + { + "default": { + "image_settings": { + "file_format": "PNG", + "color_mode": "RGB", + "color_depth": "8", + "compression": 15 + }, + "display_options": { + "shading": { + "type": "MATERIAL", + "render_pass": "COMBINED" + }, + "overlay": { + "show_overlays": False + } + } + } + }, + indent=4 + ) + } +} diff --git a/server_addon/blender/server/version.py b/server_addon/blender/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/blender/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/celaction/server/__init__.py b/server_addon/celaction/server/__init__.py new file mode 100644 index 0000000000..90d3dbaa01 --- /dev/null +++ b/server_addon/celaction/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import CelActionSettings, DEFAULT_VALUES + + +class CelActionAddon(BaseServerAddon): + name = "celaction" + title = "CelAction" + version = __version__ + settings_model: Type[CelActionSettings] = CelActionSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/celaction/server/imageio.py b/server_addon/celaction/server/imageio.py new file mode 100644 index 0000000000..72da441528 --- /dev/null +++ b/server_addon/celaction/server/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class CelActionImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/celaction/server/settings.py b/server_addon/celaction/server/settings.py new file mode 100644 index 0000000000..68d1d2dc31 --- /dev/null +++ b/server_addon/celaction/server/settings.py @@ -0,0 +1,92 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel +from .imageio import CelActionImageIOModel + + +class CollectRenderPathModel(BaseSettingsModel): + output_extension: str = Field( + "", + title="Output render file extension" + ) + anatomy_template_key_render_files: str = Field( + "", + title="Anatomy template key: render files" + ) + anatomy_template_key_metadata: str = Field( + "", + title="Anatomy template key: metadata job file" + ) + + +def _workfile_submit_overrides(): + return [ + { + "value": "render_chunk", + "label": "Pass chunk size" + }, + { + "value": "frame_range", + "label": "Pass frame range" + }, + { + "value": "resolution", + "label": "Pass resolution" + } + ] + + +class WorkfileModel(BaseSettingsModel): + submission_overrides: list[str] = Field( + default_factory=list, + title="Submission workfile overrides", + enum_resolver=_workfile_submit_overrides + ) + + +class PublishPuginsModel(BaseSettingsModel): + CollectRenderPath: CollectRenderPathModel = Field( + default_factory=CollectRenderPathModel, + title="Collect Render Path" + ) + + +class CelActionSettings(BaseSettingsModel): + imageio: CelActionImageIOModel = Field( + default_factory=CelActionImageIOModel, + title="Color Management (ImageIO)" + ) + workfile: WorkfileModel = Field( + title="Workfile" + ) + publish: PublishPuginsModel = Field( + default_factory=PublishPuginsModel, + title="Publish plugins", + ) + + +DEFAULT_VALUES = { + "imageio": { + "ocio_config": { + "enabled": False, + "filepath": [] + }, + "file_rules": { + "enabled": False, + "rules": [] + } + }, + "workfile": { + "submission_overrides": [ + "render_chunk", + "frame_range", + "resolution" + ] + }, + "publish": { + "CollectRenderPath": { + "output_extension": "png", + "anatomy_template_key_render_files": "render", + "anatomy_template_key_metadata": "render" + } + } +} diff --git a/server_addon/celaction/server/version.py b/server_addon/celaction/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/celaction/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/clockify/server/__init__.py b/server_addon/clockify/server/__init__.py new file mode 100644 index 0000000000..0fa453fdf4 --- /dev/null +++ b/server_addon/clockify/server/__init__.py @@ -0,0 +1,15 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import ClockifySettings + + +class ClockifyAddon(BaseServerAddon): + name = "clockify" + title = "Clockify" + version = __version__ + settings_model: Type[ClockifySettings] = ClockifySettings + frontend_scopes = {} + services = {} diff --git a/server_addon/clockify/server/settings.py b/server_addon/clockify/server/settings.py new file mode 100644 index 0000000000..f6891fc5b8 --- /dev/null +++ b/server_addon/clockify/server/settings.py @@ -0,0 +1,9 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class ClockifySettings(BaseSettingsModel): + workspace_name: str = Field( + "", + title="Workspace name" + ) diff --git a/server_addon/clockify/server/version.py b/server_addon/clockify/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/clockify/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/core/server/__init__.py b/server_addon/core/server/__init__.py new file mode 100644 index 0000000000..ff91f91c75 --- /dev/null +++ b/server_addon/core/server/__init__.py @@ -0,0 +1,14 @@ +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import CoreSettings, DEFAULT_VALUES + + +class CoreAddon(BaseServerAddon): + name = "core" + version = __version__ + settings_model = CoreSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/core/server/settings/__init__.py b/server_addon/core/server/settings/__init__.py new file mode 100644 index 0000000000..527a2bdc0c --- /dev/null +++ b/server_addon/core/server/settings/__init__.py @@ -0,0 +1,7 @@ +from .main import CoreSettings, DEFAULT_VALUES + + +__all__ = ( + "CoreSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/core/server/settings/main.py b/server_addon/core/server/settings/main.py new file mode 100644 index 0000000000..a1a86ae0a5 --- /dev/null +++ b/server_addon/core/server/settings/main.py @@ -0,0 +1,160 @@ +import json +from pydantic import Field, validator +from ayon_server.settings import ( + BaseSettingsModel, + MultiplatformPathListModel, + ensure_unique_names, +) +from ayon_server.exceptions import BadRequestException + +from .publish_plugins import PublishPuginsModel, DEFAULT_PUBLISH_VALUES +from .tools import GlobalToolsModel, DEFAULT_TOOLS_VALUES + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class CoreImageIOFileRulesModel(BaseSettingsModel): + activate_global_file_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class CoreImageIOConfigModel(BaseSettingsModel): + filepath: list[str] = Field(default_factory=list, title="Config path") + + +class CoreImageIOBaseModel(BaseSettingsModel): + activate_global_color_management: bool = Field( + False, + title="Override global OCIO config" + ) + ocio_config: CoreImageIOConfigModel = Field( + default_factory=CoreImageIOConfigModel, title="OCIO config" + ) + file_rules: CoreImageIOFileRulesModel = Field( + default_factory=CoreImageIOFileRulesModel, title="File Rules" + ) + + +class CoreSettings(BaseSettingsModel): + studio_name: str = Field("", title="Studio name") + studio_code: str = Field("", title="Studio code") + environments: str = Field( + "{}", + title="Global environment variables", + widget="textarea", + scope=["studio"], + ) + tools: GlobalToolsModel = Field( + default_factory=GlobalToolsModel, + title="Tools" + ) + imageio: CoreImageIOBaseModel = Field( + default_factory=CoreImageIOBaseModel, + title="Color Management (ImageIO)" + ) + publish: PublishPuginsModel = Field( + default_factory=PublishPuginsModel, + title="Publish plugins" + ) + project_plugins: MultiplatformPathListModel = Field( + default_factory=MultiplatformPathListModel, + title="Additional Project Plugin Paths", + ) + project_folder_structure: str = Field( + "{}", + widget="textarea", + title="Project folder structure", + section="---" + ) + project_environments: str = Field( + "{}", + widget="textarea", + title="Project environments", + section="---" + ) + + @validator( + "environments", + "project_folder_structure", + "project_environments") + def validate_json(cls, value): + if not value.strip(): + return "{}" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, dict) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "Environment's can't be parsed as json object" + ) + return value + + +DEFAULT_VALUES = { + "imageio": { + "activate_global_color_management": False, + "ocio_config": { + "filepath": [ + "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", + "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio" + ] + }, + "file_rules": { + "activate_global_file_rules": False, + "rules": [ + { + "name": "example", + "pattern": ".*(beauty).*", + "colorspace": "ACES - ACEScg", + "ext": "exr" + } + ] + } + }, + "studio_name": "", + "studio_code": "", + "environments": "{}", + "tools": DEFAULT_TOOLS_VALUES, + "publish": DEFAULT_PUBLISH_VALUES, + "project_folder_structure": json.dumps({ + "__project_root__": { + "prod": {}, + "resources": { + "footage": { + "plates": {}, + "offline": {} + }, + "audio": {}, + "art_dept": {} + }, + "editorial": {}, + "assets": { + "characters": {}, + "locations": {} + }, + "shots": {} + } + }, indent=4), + "project_plugins": { + "windows": [], + "darwin": [], + "linux": [] + }, + "project_environments": "{}" +} diff --git a/server_addon/core/server/settings/publish_plugins.py b/server_addon/core/server/settings/publish_plugins.py new file mode 100644 index 0000000000..c012312579 --- /dev/null +++ b/server_addon/core/server/settings/publish_plugins.py @@ -0,0 +1,959 @@ +from pydantic import Field, validator + +from ayon_server.settings import ( + BaseSettingsModel, + MultiplatformPathModel, + normalize_name, + ensure_unique_names, + task_types_enum, +) + +from ayon_server.types import ColorRGBA_uint8 + + +class ValidateBaseModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + optional: bool = Field(True, title="Optional") + active: bool = Field(True, title="Active") + + +class CollectAnatomyInstanceDataModel(BaseSettingsModel): + _isGroup = True + follow_workfile_version: bool = Field( + True, title="Collect Anatomy Instance Data" + ) + + +class CollectAudioModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + audio_product_name: str = Field( + "", title="Name of audio variant" + ) + + +class CollectSceneVersionModel(BaseSettingsModel): + _isGroup = True + hosts: list[str] = Field( + default_factory=list, + title="Host names" + ) + skip_hosts_headless_publish: list[str] = Field( + default_factory=list, + title="Skip for host if headless publish" + ) + + +class CollectCommentPIModel(BaseSettingsModel): + enabled: bool = Field(True) + families: list[str] = Field(default_factory=list, title="Families") + + +class CollectFramesFixDefModel(BaseSettingsModel): + enabled: bool = Field(True) + rewrite_version_enable: bool = Field( + True, + title="Show 'Rewrite latest version' toggle" + ) + + +class ValidateIntentProfile(BaseSettingsModel): + _layout = "expanded" + hosts: list[str] = Field(default_factory=list, title="Host names") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field(default_factory=list, title="Task names") + # TODO This was 'validate' in v3 + validate_intent: bool = Field(True, title="Validate") + + +class ValidateIntentModel(BaseSettingsModel): + """Validate if Publishing intent was selected. + + It is possible to disable validation for specific publishing context + with profiles. + """ + + _isGroup = True + enabled: bool = Field(False) + profiles: list[ValidateIntentProfile] = Field(default_factory=list) + + +class ExtractThumbnailFFmpegModel(BaseSettingsModel): + _layout = "expanded" + input: list[str] = Field( + default_factory=list, + title="FFmpeg input arguments" + ) + output: list[str] = Field( + default_factory=list, + title="FFmpeg input arguments" + ) + + +class ExtractThumbnailModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + ffmpeg_args: ExtractThumbnailFFmpegModel = Field( + default_factory=ExtractThumbnailFFmpegModel + ) + + +def _extract_oiio_transcoding_type(): + return [ + {"value": "colorspace", "label": "Use Colorspace"}, + {"value": "display", "label": "Use Display&View"} + ] + + +class OIIOToolArgumentsModel(BaseSettingsModel): + additional_command_args: list[str] = Field( + default_factory=list, title="Arguments") + + +class ExtractOIIOTranscodeOutputModel(BaseSettingsModel): + extension: str = Field("", title="Extension") + transcoding_type: str = Field( + "colorspace", + title="Transcoding type", + enum_resolver=_extract_oiio_transcoding_type + ) + colorspace: str = Field("", title="Colorspace") + display: str = Field("", title="Display") + view: str = Field("", title="View") + oiiotool_args: OIIOToolArgumentsModel = Field( + default_factory=OIIOToolArgumentsModel, + title="OIIOtool arguments") + + tags: list[str] = Field(default_factory=list, title="Tags") + custom_tags: list[str] = Field(default_factory=list, title="Custom Tags") + + +class ExtractOIIOTranscodeProfileModel(BaseSettingsModel): + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + hosts: list[str] = Field( + default_factory=list, + title="Host names" + ) + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field( + default_factory=list, + title="Task names" + ) + product_names: list[str] = Field( + default_factory=list, + title="Product names" + ) + delete_original: bool = Field( + True, + title="Delete Original Representation" + ) + outputs: list[ExtractOIIOTranscodeOutputModel] = Field( + default_factory=list, + title="Output Definitions", + ) + + +class ExtractOIIOTranscodeModel(BaseSettingsModel): + enabled: bool = Field(True) + profiles: list[ExtractOIIOTranscodeProfileModel] = Field( + default_factory=list, title="Profiles" + ) + + +# --- [START] Extract Review --- +class ExtractReviewFFmpegModel(BaseSettingsModel): + video_filters: list[str] = Field( + default_factory=list, + title="Video filters" + ) + audio_filters: list[str] = Field( + default_factory=list, + title="Audio filters" + ) + input: list[str] = Field( + default_factory=list, + title="Input arguments" + ) + output: list[str] = Field( + default_factory=list, + title="Output arguments" + ) + + +def extract_review_filter_enum(): + return [ + { + "value": "everytime", + "label": "Always" + }, + { + "value": "single_frame", + "label": "Only if input has 1 image frame" + }, + { + "value": "multi_frame", + "label": "Only if input is video or sequence of frames" + } + ] + + +class ExtractReviewFilterModel(BaseSettingsModel): + families: list[str] = Field(default_factory=list, title="Families") + product_names: list[str] = Field( + default_factory=list, title="Product names") + custom_tags: list[str] = Field(default_factory=list, title="Custom Tags") + single_frame_filter: str = Field( + "everytime", + description=( + "Use output always / only if input is 1 frame" + " image / only if has 2+ frames or is video" + ), + enum_resolver=extract_review_filter_enum + ) + + +class ExtractReviewLetterBox(BaseSettingsModel): + enabled: bool = Field(True) + ratio: float = Field( + 0.0, + title="Ratio", + ge=0.0, + le=10000.0 + ) + fill_color: ColorRGBA_uint8 = Field( + (0, 0, 0, 0.0), + title="Fill Color" + ) + line_thickness: int = Field( + 0, + title="Line Thickness", + ge=0, + le=1000 + ) + line_color: ColorRGBA_uint8 = Field( + (0, 0, 0, 0.0), + title="Line Color" + ) + + +class ExtractReviewOutputDefModel(BaseSettingsModel): + _layout = "expanded" + name: str = Field("", title="Name") + ext: str = Field("", title="Output extension") + # TODO use some different source of tags + tags: list[str] = Field(default_factory=list, title="Tags") + burnins: list[str] = Field( + default_factory=list, title="Link to a burnin by name" + ) + ffmpeg_args: ExtractReviewFFmpegModel = Field( + default_factory=ExtractReviewFFmpegModel, + title="FFmpeg arguments" + ) + filter: ExtractReviewFilterModel = Field( + default_factory=ExtractReviewFilterModel, + title="Additional output filtering" + ) + overscan_crop: str = Field( + "", + title="Overscan crop", + description=( + "Crop input overscan. See the documentation for more information." + ) + ) + overscan_color: ColorRGBA_uint8 = Field( + (0, 0, 0, 0.0), + title="Overscan color", + description=( + "Overscan color is used when input aspect ratio is not" + " same as output aspect ratio." + ) + ) + width: int = Field( + 0, + ge=0, + le=100000, + title="Output width", + description=( + "Width and Height must be both set to higher" + " value than 0 else source resolution is used." + ) + ) + height: int = Field( + 0, + title="Output height", + ge=0, + le=100000, + ) + scale_pixel_aspect: bool = Field( + True, + title="Scale pixel aspect", + description=( + "Rescale input when it's pixel aspect ratio is not 1." + " Usefull for anamorph reviews." + ) + ) + bg_color: ColorRGBA_uint8 = Field( + (0, 0, 0, 0.0), + description=( + "Background color is used only when input have transparency" + " and Alpha is higher than 0." + ), + title="Background color", + ) + letter_box: ExtractReviewLetterBox = Field( + default_factory=ExtractReviewLetterBox, + title="Letter Box" + ) + + @validator("name") + def validate_name(cls, value): + """Ensure name does not contain weird characters""" + return normalize_name(value) + + +class ExtractReviewProfileModel(BaseSettingsModel): + _layout = "expanded" + product_types: list[str] = Field( + default_factory=list, title="Product types" + ) + # TODO use hosts enum + hosts: list[str] = Field( + default_factory=list, title="Host names" + ) + outputs: list[ExtractReviewOutputDefModel] = Field( + default_factory=list, title="Output Definitions" + ) + + @validator("outputs") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ExtractReviewModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + profiles: list[ExtractReviewProfileModel] = Field( + default_factory=list, + title="Profiles" + ) +# --- [END] Extract Review --- + + +# --- [Start] Extract Burnin --- +class ExtractBurninOptionsModel(BaseSettingsModel): + font_size: int = Field(0, ge=0, title="Font size") + font_color: ColorRGBA_uint8 = Field( + (255, 255, 255, 1.0), + title="Font color" + ) + bg_color: ColorRGBA_uint8 = Field( + (0, 0, 0, 1.0), + title="Background color" + ) + x_offset: int = Field(0, title="X Offset") + y_offset: int = Field(0, title="Y Offset") + bg_padding: int = Field(0, title="Padding around text") + font_filepath: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Font file path" + ) + + +class ExtractBurninDefFilter(BaseSettingsModel): + families: list[str] = Field( + default_factory=list, + title="Families" + ) + tags: list[str] = Field( + default_factory=list, + title="Tags" + ) + + +class ExtractBurninDef(BaseSettingsModel): + _isGroup = True + _layout = "expanded" + name: str = Field("") + TOP_LEFT: str = Field("", topic="Top Left") + TOP_CENTERED: str = Field("", topic="Top Centered") + TOP_RIGHT: str = Field("", topic="Top Right") + BOTTOM_LEFT: str = Field("", topic="Bottom Left") + BOTTOM_CENTERED: str = Field("", topic="Bottom Centered") + BOTTOM_RIGHT: str = Field("", topic="Bottom Right") + filter: ExtractBurninDefFilter = Field( + default_factory=ExtractBurninDefFilter, + title="Additional filtering" + ) + + @validator("name") + def validate_name(cls, value): + """Ensure name does not contain weird characters""" + return normalize_name(value) + + +class ExtractBurninProfile(BaseSettingsModel): + _layout = "expanded" + product_types: list[str] = Field( + default_factory=list, + title="Produt types" + ) + hosts: list[str] = Field( + default_factory=list, + title="Host names" + ) + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field( + default_factory=list, + title="Task names" + ) + product_names: list[str] = Field( + default_factory=list, + title="Product names" + ) + burnins: list[ExtractBurninDef] = Field( + default_factory=list, + title="Burnins" + ) + + @validator("burnins") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + + return value + + +class ExtractBurninModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + options: ExtractBurninOptionsModel = Field( + default_factory=ExtractBurninOptionsModel, + title="Burnin formatting options" + ) + profiles: list[ExtractBurninProfile] = Field( + default_factory=list, + title="Profiles" + ) +# --- [END] Extract Burnin --- + + +class PreIntegrateThumbnailsProfile(BaseSettingsModel): + _isGroup = True + product_types: list[str] = Field( + default_factory=list, + title="Product types", + ) + hosts: list[str] = Field( + default_factory=list, + title="Hosts", + ) + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + product_names: list[str] = Field( + default_factory=list, + title="Product names", + ) + integrate_thumbnail: bool = Field(True) + + +class PreIntegrateThumbnailsModel(BaseSettingsModel): + """Explicitly set if Thumbnail representation should be integrated. + + If no matching profile set, existing state from Host implementation + is kept. + """ + + _isGroup = True + enabled: bool = Field(True) + integrate_profiles: list[PreIntegrateThumbnailsProfile] = Field( + default_factory=list, + title="Integrate profiles" + ) + + +class IntegrateProductGroupProfile(BaseSettingsModel): + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field(default_factory=list, title="Task names") + template: str = Field("", title="Template") + + +class IntegrateProductGroupModel(BaseSettingsModel): + """Group published products by filtering logic. + + Set all published instances as a part of specific group named according + to 'Template'. + + Implemented all variants of placeholders '{task}', '{product[type]}', + '{host}', '{product[name]}', '{renderlayer}'. + """ + + _isGroup = True + product_grouping_profiles: list[IntegrateProductGroupProfile] = Field( + default_factory=list, + title="Product group profiles" + ) + + +class IntegrateANProductGroupProfileModel(BaseSettingsModel): + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + hosts: list[str] = Field( + default_factory=list, + title="Hosts" + ) + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field( + default_factory=list, + title="Task names" + ) + template: str = Field("", title="Template") + + +class IntegrateANTemplateNameProfileModel(BaseSettingsModel): + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + hosts: list[str] = Field( + default_factory=list, + title="Hosts" + ) + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field( + default_factory=list, + title="Task names" + ) + template_name: str = Field("", title="Template name") + + +class IntegrateHeroTemplateNameProfileModel(BaseSettingsModel): + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + hosts: list[str] = Field( + default_factory=list, + title="Hosts" + ) + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field( + default_factory=list, + title="Task names" + ) + template_name: str = Field("", title="Template name") + + +class IntegrateHeroVersionModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + families: list[str] = Field(default_factory=list, title="Families") + # TODO remove when removed from client code + template_name_profiles: list[IntegrateHeroTemplateNameProfileModel] = ( + Field( + default_factory=list, + title="Template name profiles" + ) + ) + + +class CleanUpModel(BaseSettingsModel): + _isGroup = True + paterns: list[str] = Field( + default_factory=list, + title="Patterns (regex)" + ) + remove_temp_renders: bool = Field(False, title="Remove Temp renders") + + +class CleanUpFarmModel(BaseSettingsModel): + _isGroup = True + enabled: bool = Field(True) + + +class PublishPuginsModel(BaseSettingsModel): + CollectAnatomyInstanceData: CollectAnatomyInstanceDataModel = Field( + default_factory=CollectAnatomyInstanceDataModel, + title="Collect Anatomy Instance Data" + ) + CollectAudio: CollectAudioModel = Field( + default_factory=CollectAudioModel, + title="Collect Audio" + ) + CollectSceneVersion: CollectSceneVersionModel = Field( + default_factory=CollectSceneVersionModel, + title="Collect Version from Workfile" + ) + collect_comment_per_instance: CollectCommentPIModel = Field( + default_factory=CollectCommentPIModel, + title="Collect comment per instance", + ) + CollectFramesFixDef: CollectFramesFixDefModel = Field( + default_factory=CollectFramesFixDefModel, + title="Collect Frames to Fix", + ) + ValidateEditorialAssetName: ValidateBaseModel = Field( + default_factory=ValidateBaseModel, + title="Validate Editorial Asset Name" + ) + ValidateVersion: ValidateBaseModel = Field( + default_factory=ValidateBaseModel, + title="Validate Version" + ) + ValidateIntent: ValidateIntentModel = Field( + default_factory=ValidateIntentModel, + title="Validate Intent" + ) + ExtractThumbnail: ExtractThumbnailModel = Field( + default_factory=ExtractThumbnailModel, + title="Extract Thumbnail" + ) + ExtractOIIOTranscode: ExtractOIIOTranscodeModel = Field( + default_factory=ExtractOIIOTranscodeModel, + title="Extract OIIO Transcode" + ) + ExtractReview: ExtractReviewModel = Field( + default_factory=ExtractReviewModel, + title="Extract Review" + ) + ExtractBurnin: ExtractBurninModel = Field( + default_factory=ExtractBurninModel, + title="Extract Burnin" + ) + PreIntegrateThumbnails: PreIntegrateThumbnailsModel = Field( + default_factory=PreIntegrateThumbnailsModel, + title="Override Integrate Thumbnail Representations" + ) + IntegrateProductGroup: IntegrateProductGroupModel = Field( + default_factory=IntegrateProductGroupModel, + title="Integrate Product Group" + ) + IntegrateHeroVersion: IntegrateHeroVersionModel = Field( + default_factory=IntegrateHeroVersionModel, + title="Integrate Hero Version" + ) + CleanUp: CleanUpModel = Field( + default_factory=CleanUpModel, + title="Clean Up" + ) + CleanUpFarm: CleanUpFarmModel = Field( + default_factory=CleanUpFarmModel, + title="Clean Up Farm" + ) + + +DEFAULT_PUBLISH_VALUES = { + "CollectAnatomyInstanceData": { + "follow_workfile_version": False + }, + "CollectAudio": { + "enabled": False, + "audio_product_name": "audioMain" + }, + "CollectSceneVersion": { + "hosts": [ + "aftereffects", + "blender", + "celaction", + "fusion", + "harmony", + "hiero", + "houdini", + "maya", + "nuke", + "photoshop", + "resolve", + "tvpaint" + ], + "skip_hosts_headless_publish": [] + }, + "collect_comment_per_instance": { + "enabled": False, + "families": [] + }, + "CollectFramesFixDef": { + "enabled": True, + "rewrite_version_enable": True + }, + "ValidateEditorialAssetName": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateVersion": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateIntent": { + "enabled": False, + "profiles": [] + }, + "ExtractThumbnail": { + "enabled": True, + "ffmpeg_args": { + "input": [ + "-apply_trc gamma22" + ], + "output": [] + } + }, + "ExtractOIIOTranscode": { + "enabled": True, + "profiles": [] + }, + "ExtractReview": { + "enabled": True, + "profiles": [ + { + "product_types": [], + "hosts": [], + "outputs": [ + { + "name": "png", + "ext": "png", + "tags": [ + "ftrackreview", + "kitsureview" + ], + "burnins": [], + "ffmpeg_args": { + "video_filters": [], + "audio_filters": [], + "input": [], + "output": [] + }, + "filter": { + "families": [ + "render", + "review", + "ftrack" + ], + "product_names": [], + "custom_tags": [], + "single_frame_filter": "single_frame" + }, + "overscan_crop": "", + "overscan_color": [0, 0, 0, 1.0], + "width": 1920, + "height": 1080, + "scale_pixel_aspect": True, + "bg_color": [0, 0, 0, 0.0], + "letter_box": { + "enabled": False, + "ratio": 0.0, + "fill_color": [0, 0, 0, 1.0], + "line_thickness": 0, + "line_color": [255, 0, 0, 1.0] + } + }, + { + "name": "h264", + "ext": "mp4", + "tags": [ + "burnin", + "ftrackreview", + "kitsureview" + ], + "burnins": [], + "ffmpeg_args": { + "video_filters": [], + "audio_filters": [], + "input": [ + "-apply_trc gamma22" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + }, + "filter": { + "families": [ + "render", + "review", + "ftrack" + ], + "product_names": [], + "custom_tags": [], + "single_frame_filter": "multi_frame" + }, + "overscan_crop": "", + "overscan_color": [0, 0, 0, 1.0], + "width": 0, + "height": 0, + "scale_pixel_aspect": True, + "bg_color": [0, 0, 0, 0.0], + "letter_box": { + "enabled": False, + "ratio": 0.0, + "fill_color": [0, 0, 0, 1.0], + "line_thickness": 0, + "line_color": [255, 0, 0, 1.0] + } + } + ] + } + ] + }, + "ExtractBurnin": { + "enabled": True, + "options": { + "font_size": 42, + "font_color": [255, 255, 255, 1.0], + "bg_color": [0, 0, 0, 0.5], + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5, + "font_filepath": { + "windows": "", + "darwin": "", + "linux": "" + } + }, + "profiles": [ + { + "product_types": [], + "hosts": [], + "task_types": [], + "task_names": [], + "product_names": [], + "burnins": [ + { + "name": "burnin", + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_CENTERED": "", + "TOP_RIGHT": "{anatomy[version]}", + "BOTTOM_LEFT": "{username}", + "BOTTOM_CENTERED": "{folder[name]}", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "filter": { + "families": [], + "tags": [] + } + }, + ] + }, + { + "product_types": ["review"], + "hosts": [ + "maya", + "houdini", + "max" + ], + "task_types": [], + "task_names": [], + "product_names": [], + "burnins": [ + { + "name": "focal_length_burnin", + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_CENTERED": "{focalLength:.2f} mm", + "TOP_RIGHT": "{anatomy[version]}", + "BOTTOM_LEFT": "{username}", + "BOTTOM_CENTERED": "{folder[name]}", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "filter": { + "families": [], + "tags": [] + } + } + ] + } + ] + }, + "PreIntegrateThumbnails": { + "enabled": True, + "integrate_profiles": [] + }, + "IntegrateProductGroup": { + "product_grouping_profiles": [ + { + "product_types": [], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "" + } + ] + }, + "IntegrateHeroVersion": { + "enabled": True, + "optional": True, + "active": True, + "families": [ + "model", + "rig", + "look", + "pointcache", + "animation", + "setdress", + "layout", + "mayaScene", + "simpleUnrealTexture" + ], + "template_name_profiles": [ + { + "product_types": [ + "simpleUnrealTexture" + ], + "hosts": [ + "standalonepublisher" + ], + "task_types": [], + "task_names": [], + "template_name": "simpleUnrealTextureHero" + } + ] + }, + "CleanUp": { + "paterns": [], + "remove_temp_renders": False + }, + "CleanUpFarm": { + "enabled": False + } +} diff --git a/server_addon/core/server/settings/tools.py b/server_addon/core/server/settings/tools.py new file mode 100644 index 0000000000..7befc795e4 --- /dev/null +++ b/server_addon/core/server/settings/tools.py @@ -0,0 +1,506 @@ +from pydantic import Field, validator +from ayon_server.settings import ( + BaseSettingsModel, + normalize_name, + ensure_unique_names, + task_types_enum, +) + + +class ProductTypeSmartSelectModel(BaseSettingsModel): + _layout = "expanded" + name: str = Field("", title="Product type") + task_names: list[str] = Field(default_factory=list, title="Task names") + + @validator("name") + def normalize_value(cls, value): + return normalize_name(value) + + +class ProductNameProfile(BaseSettingsModel): + _layout = "expanded" + product_types: list[str] = Field( + default_factory=list, title="Product types" + ) + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field(default_factory=list, title="Task names") + template: str = Field("", title="Template") + + +class CreatorToolModel(BaseSettingsModel): + # TODO this was dynamic dictionary '{name: task_names}' + product_types_smart_select: list[ProductTypeSmartSelectModel] = Field( + default_factory=list, + title="Create Smart Select" + ) + product_name_profiles: list[ProductNameProfile] = Field( + default_factory=list, + title="Product name profiles" + ) + + @validator("product_types_smart_select") + def validate_unique_name(cls, value): + ensure_unique_names(value) + return value + + +class WorkfileTemplateProfile(BaseSettingsModel): + _layout = "expanded" + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + # TODO this should use hosts enum + hosts: list[str] = Field(default_factory=list, title="Hosts") + # TODO this was using project anatomy template name + workfile_template: str = Field("", title="Workfile template") + + +class LastWorkfileOnStartupProfile(BaseSettingsModel): + _layout = "expanded" + # TODO this should use hosts enum + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field(default_factory=list, title="Task names") + enabled: bool = Field(True, title="Enabled") + use_last_published_workfile: bool = Field( + True, title="Use last published workfile" + ) + + +class WorkfilesToolOnStartupProfile(BaseSettingsModel): + _layout = "expanded" + # TODO this should use hosts enum + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field(default_factory=list, title="Task names") + enabled: bool = Field(True, title="Enabled") + + +class ExtraWorkFoldersProfile(BaseSettingsModel): + _layout = "expanded" + # TODO this should use hosts enum + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field(default_factory=list, title="Task names") + folders: list[str] = Field(default_factory=list, title="Folders") + + +class WorkfilesLockProfile(BaseSettingsModel): + _layout = "expanded" + # TODO this should use hosts enum + host_names: list[str] = Field(default_factory=list, title="Hosts") + enabled: bool = Field(True, title="Enabled") + + +class WorkfilesToolModel(BaseSettingsModel): + workfile_template_profiles: list[WorkfileTemplateProfile] = Field( + default_factory=list, + title="Workfile template profiles" + ) + last_workfile_on_startup: list[LastWorkfileOnStartupProfile] = Field( + default_factory=list, + title="Open last workfile on launch" + ) + open_workfile_tool_on_startup: list[WorkfilesToolOnStartupProfile] = Field( + default_factory=list, + title="Open workfile tool on launch" + ) + extra_folders: list[ExtraWorkFoldersProfile] = Field( + default_factory=list, + title="Extra work folders" + ) + workfile_lock_profiles: list[WorkfilesLockProfile] = Field( + default_factory=list, + title="Workfile lock profiles" + ) + + +def _product_types_enum(): + return [ + "action", + "animation", + "assembly", + "audio", + "backgroundComp", + "backgroundLayout", + "camera", + "editorial", + "gizmo", + "image", + "layout", + "look", + "matchmove", + "mayaScene", + "model", + "nukenodes", + "plate", + "pointcache", + "prerender", + "redshiftproxy", + "reference", + "render", + "review", + "rig", + "setdress", + "take", + "usdShade", + "vdbcache", + "vrayproxy", + "workfile", + "xgen", + "yetiRig", + "yeticache" + ] + + +class LoaderProductTypeFilterProfile(BaseSettingsModel): + _layout = "expanded" + # TODO this should use hosts enum + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + is_include: bool = Field(True, title="Exclude / Include") + filter_product_types: list[str] = Field( + default_factory=list, + enum_resolver=_product_types_enum + ) + + +class LoaderToolModel(BaseSettingsModel): + product_type_filter_profiles: list[LoaderProductTypeFilterProfile] = Field( + default_factory=list, + title="Product type filtering" + ) + + +class PublishTemplateNameProfile(BaseSettingsModel): + _layout = "expanded" + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + # TODO this should use hosts enum + hosts: list[str] = Field(default_factory=list, title="Hosts") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field(default_factory=list, title="Task names") + template_name: str = Field("", title="Template name") + + +class CustomStagingDirProfileModel(BaseSettingsModel): + active: bool = Field(True, title="Is active") + hosts: list[str] = Field(default_factory=list, title="Host names") + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field( + default_factory=list, title="Task names" + ) + product_types: list[str] = Field( + default_factory=list, title="Product types" + ) + product_names: list[str] = Field( + default_factory=list, title="Product names" + ) + custom_staging_dir_persistent: bool = Field( + False, title="Custom Staging Folder Persistent" + ) + template_name: str = Field("", title="Template Name") + + +class PublishToolModel(BaseSettingsModel): + template_name_profiles: list[PublishTemplateNameProfile] = Field( + default_factory=list, + title="Template name profiles" + ) + hero_template_name_profiles: list[PublishTemplateNameProfile] = Field( + default_factory=list, + title="Hero template name profiles" + ) + custom_staging_dir_profiles: list[CustomStagingDirProfileModel] = Field( + default_factory=list, + title="Custom Staging Dir Profiles" + ) + + +class GlobalToolsModel(BaseSettingsModel): + creator: CreatorToolModel = Field( + default_factory=CreatorToolModel, + title="Creator" + ) + Workfiles: WorkfilesToolModel = Field( + default_factory=WorkfilesToolModel, + title="Workfiles" + ) + loader: LoaderToolModel = Field( + default_factory=LoaderToolModel, + title="Loader" + ) + publish: PublishToolModel = Field( + default_factory=PublishToolModel, + title="Publish" + ) + + +DEFAULT_TOOLS_VALUES = { + "creator": { + "product_types_smart_select": [ + { + "name": "Render", + "task_names": [ + "light", + "render" + ] + }, + { + "name": "Model", + "task_names": [ + "model" + ] + }, + { + "name": "Layout", + "task_names": [ + "layout" + ] + }, + { + "name": "Look", + "task_names": [ + "look" + ] + }, + { + "name": "Rig", + "task_names": [ + "rigging", + "rig" + ] + } + ], + "product_name_profiles": [ + { + "product_types": [], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "{product[type]}{variant}" + }, + { + "product_types": [ + "workfile" + ], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "{product[type]}{Task[name]}" + }, + { + "product_types": [ + "render" + ], + "hosts": [], + "task_types": [], + "tasks": [], + "template": "{product[type]}{Task[name]}{Variant}" + }, + { + "product_types": [ + "renderLayer", + "renderPass" + ], + "hosts": [ + "tvpaint" + ], + "task_types": [], + "tasks": [], + "template": "{product[type]}{Task[name]}_{Renderlayer}_{Renderpass}" + }, + { + "product_types": [ + "review", + "workfile" + ], + "hosts": [ + "aftereffects", + "tvpaint" + ], + "task_types": [], + "tasks": [], + "template": "{product[type]}{Task[name]}" + }, + { + "product_types": ["render"], + "hosts": [ + "aftereffects" + ], + "task_types": [], + "tasks": [], + "template": "{product[type]}{Task[name]}{Composition}{Variant}" + }, + { + "product_types": [ + "staticMesh" + ], + "hosts": [ + "maya" + ], + "task_types": [], + "tasks": [], + "template": "S_{folder[name]}{variant}" + }, + { + "product_types": [ + "skeletalMesh" + ], + "hosts": [ + "maya" + ], + "task_types": [], + "tasks": [], + "template": "SK_{folder[name]}{variant}" + } + ] + }, + "Workfiles": { + "workfile_template_profiles": [ + { + "task_types": [], + "hosts": [], + "workfile_template": "work" + }, + { + "task_types": [], + "hosts": [ + "unreal" + ], + "workfile_template": "work_unreal" + } + ], + "last_workfile_on_startup": [ + { + "hosts": [], + "task_types": [], + "tasks": [], + "enabled": True, + "use_last_published_workfile": False + } + ], + "open_workfile_tool_on_startup": [ + { + "hosts": [], + "task_types": [], + "tasks": [], + "enabled": False + } + ], + "extra_folders": [], + "workfile_lock_profiles": [] + }, + "loader": { + "product_type_filter_profiles": [ + { + "hosts": [], + "task_types": [], + "is_include": True, + "filter_product_types": [] + } + ] + }, + "publish": { + "template_name_profiles": [ + { + "product_types": [], + "hosts": [], + "task_types": [], + "task_names": [], + "template_name": "publish" + }, + { + "product_types": [ + "review", + "render", + "prerender" + ], + "hosts": [], + "task_types": [], + "task_names": [], + "template_name": "publish_render" + }, + { + "product_types": [ + "simpleUnrealTexture" + ], + "hosts": [ + "standalonepublisher" + ], + "task_types": [], + "task_names": [], + "template_name": "publish_simpleUnrealTexture" + }, + { + "product_types": [ + "staticMesh", + "skeletalMesh" + ], + "hosts": [ + "maya" + ], + "task_types": [], + "task_names": [], + "template_name": "publish_maya2unreal" + }, + { + "product_types": [ + "online" + ], + "hosts": [ + "traypublisher" + ], + "task_types": [], + "task_names": [], + "template_name": "publish_online" + } + ], + "hero_template_name_profiles": [ + { + "product_types": [ + "simpleUnrealTexture" + ], + "hosts": [ + "standalonepublisher" + ], + "task_types": [], + "task_names": [], + "template_name": "hero_simpleUnrealTextureHero" + } + ] + } +} diff --git a/server_addon/core/server/version.py b/server_addon/core/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/core/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/create_ayon_addon.py b/server_addon/create_ayon_addon.py deleted file mode 100644 index 657f416441..0000000000 --- a/server_addon/create_ayon_addon.py +++ /dev/null @@ -1,140 +0,0 @@ -import os -import re -import shutil -import zipfile -import collections -from pathlib import Path -from typing import Any, Optional, Iterable - -# Patterns of directories to be skipped for server part of addon -IGNORE_DIR_PATTERNS: list[re.Pattern] = [ - re.compile(pattern) - for pattern in { - # Skip directories starting with '.' - r"^\.", - # Skip any pycache folders - "^__pycache__$" - } -] - -# Patterns of files to be skipped for server part of addon -IGNORE_FILE_PATTERNS: list[re.Pattern] = [ - re.compile(pattern) - for pattern in { - # Skip files starting with '.' - # NOTE this could be an issue in some cases - r"^\.", - # Skip '.pyc' files - r"\.pyc$" - } -] - - -def _value_match_regexes(value: str, regexes: Iterable[re.Pattern]) -> bool: - return any( - regex.search(value) - for regex in regexes - ) - - -def find_files_in_subdir( - src_path: str, - ignore_file_patterns: Optional[list[re.Pattern]] = None, - ignore_dir_patterns: Optional[list[re.Pattern]] = None -): - """Find all files to copy in subdirectories of given path. - - All files that match any of the patterns in 'ignore_file_patterns' will - be skipped and any directories that match any of the patterns in - 'ignore_dir_patterns' will be skipped with all subfiles. - - Args: - src_path (str): Path to directory to search in. - ignore_file_patterns (Optional[list[re.Pattern]]): List of regexes - to match files to ignore. - ignore_dir_patterns (Optional[list[re.Pattern]]): List of regexes - to match directories to ignore. - - Returns: - list[tuple[str, str]]: List of tuples with path to file and parent - directories relative to 'src_path'. - """ - - if ignore_file_patterns is None: - ignore_file_patterns = IGNORE_FILE_PATTERNS - - if ignore_dir_patterns is None: - ignore_dir_patterns = IGNORE_DIR_PATTERNS - output: list[tuple[str, str]] = [] - - hierarchy_queue = collections.deque() - hierarchy_queue.append((src_path, [])) - while hierarchy_queue: - item: tuple[str, str] = hierarchy_queue.popleft() - dirpath, parents = item - for name in os.listdir(dirpath): - path = os.path.join(dirpath, name) - if os.path.isfile(path): - if not _value_match_regexes(name, ignore_file_patterns): - items = list(parents) - items.append(name) - output.append((path, os.path.sep.join(items))) - continue - - if not _value_match_regexes(name, ignore_dir_patterns): - items = list(parents) - items.append(name) - hierarchy_queue.append((path, items)) - - return output - - -def main(): - openpype_addon_dir = Path(os.path.dirname(os.path.abspath(__file__))) - server_dir = openpype_addon_dir / "server" - package_root = openpype_addon_dir / "package" - pyproject_path = openpype_addon_dir / "client" / "pyproject.toml" - - root_dir = openpype_addon_dir.parent - openpype_dir = root_dir / "openpype" - version_path = openpype_dir / "version.py" - - # Read version - version_content: dict[str, Any] = {} - with open(str(version_path), "r") as stream: - exec(stream.read(), version_content) - addon_version: str = version_content["__version__"] - - output_dir = package_root / "openpype" / addon_version - private_dir = output_dir / "private" - - # Make sure package dir is empty - if package_root.exists(): - shutil.rmtree(str(package_root)) - # Make sure output dir is created - output_dir.mkdir(parents=True) - - # Copy version - shutil.copy(str(version_path), str(output_dir)) - for subitem in server_dir.iterdir(): - shutil.copy(str(subitem), str(output_dir / subitem.name)) - - # Make sure private dir exists - private_dir.mkdir(parents=True) - - # Copy pyproject.toml - shutil.copy( - str(pyproject_path), - (private_dir / pyproject_path.name) - ) - - # Zip client - zip_filepath = private_dir / "client.zip" - with zipfile.ZipFile(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: - # Add client code content to zip - for path, sub_path in find_files_in_subdir(str(openpype_dir)): - zipf.write(path, f"{openpype_dir.name}/{sub_path}") - - -if __name__ == "__main__": - main() diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py new file mode 100644 index 0000000000..3b566cec63 --- /dev/null +++ b/server_addon/create_ayon_addons.py @@ -0,0 +1,279 @@ +import os +import sys +import re +import json +import shutil +import zipfile +import platform +import collections +from pathlib import Path +from typing import Any, Optional, Iterable + +# Patterns of directories to be skipped for server part of addon +IGNORE_DIR_PATTERNS: list[re.Pattern] = [ + re.compile(pattern) + for pattern in { + # Skip directories starting with '.' + r"^\.", + # Skip any pycache folders + "^__pycache__$" + } +] + +# Patterns of files to be skipped for server part of addon +IGNORE_FILE_PATTERNS: list[re.Pattern] = [ + re.compile(pattern) + for pattern in { + # Skip files starting with '.' + # NOTE this could be an issue in some cases + r"^\.", + # Skip '.pyc' files + r"\.pyc$" + } +] + + +class ZipFileLongPaths(zipfile.ZipFile): + """Allows longer paths in zip files. + + Regular DOS paths are limited to MAX_PATH (260) characters, including + the string's terminating NUL character. + That limit can be exceeded by using an extended-length path that + starts with the '\\?\' prefix. + """ + _is_windows = platform.system().lower() == "windows" + + def _extract_member(self, member, tpath, pwd): + if self._is_windows: + tpath = os.path.abspath(tpath) + if tpath.startswith("\\\\"): + tpath = "\\\\?\\UNC\\" + tpath[2:] + else: + tpath = "\\\\?\\" + tpath + + return super(ZipFileLongPaths, self)._extract_member( + member, tpath, pwd + ) + + +def _value_match_regexes(value: str, regexes: Iterable[re.Pattern]) -> bool: + return any( + regex.search(value) + for regex in regexes + ) + + +def find_files_in_subdir( + src_path: str, + ignore_file_patterns: Optional[list[re.Pattern]] = None, + ignore_dir_patterns: Optional[list[re.Pattern]] = None +): + """Find all files to copy in subdirectories of given path. + + All files that match any of the patterns in 'ignore_file_patterns' will + be skipped and any directories that match any of the patterns in + 'ignore_dir_patterns' will be skipped with all subfiles. + + Args: + src_path (str): Path to directory to search in. + ignore_file_patterns (Optional[list[re.Pattern]]): List of regexes + to match files to ignore. + ignore_dir_patterns (Optional[list[re.Pattern]]): List of regexes + to match directories to ignore. + + Returns: + list[tuple[str, str]]: List of tuples with path to file and parent + directories relative to 'src_path'. + """ + + if ignore_file_patterns is None: + ignore_file_patterns = IGNORE_FILE_PATTERNS + + if ignore_dir_patterns is None: + ignore_dir_patterns = IGNORE_DIR_PATTERNS + output: list[tuple[str, str]] = [] + + hierarchy_queue = collections.deque() + hierarchy_queue.append((src_path, [])) + while hierarchy_queue: + item: tuple[str, str] = hierarchy_queue.popleft() + dirpath, parents = item + for name in os.listdir(dirpath): + path = os.path.join(dirpath, name) + if os.path.isfile(path): + if not _value_match_regexes(name, ignore_file_patterns): + items = list(parents) + items.append(name) + output.append((path, os.path.sep.join(items))) + continue + + if not _value_match_regexes(name, ignore_dir_patterns): + items = list(parents) + items.append(name) + hierarchy_queue.append((path, items)) + + return output + + +def read_addon_version(version_path: Path) -> str: + # Read version + version_content: dict[str, Any] = {} + with open(str(version_path), "r") as stream: + exec(stream.read(), version_content) + return version_content["__version__"] + + +def get_addon_version(addon_dir: Path) -> str: + return read_addon_version(addon_dir / "server" / "version.py") + + +def create_addon_zip( + output_dir: Path, + addon_name: str, + addon_version: str, + keep_source: bool +): + zip_filepath = output_dir / f"{addon_name}.zip" + addon_output_dir = output_dir / addon_name / addon_version + with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: + zipf.writestr( + "manifest.json", + json.dumps({ + "addon_name": addon_name, + "addon_version": addon_version + }) + ) + # Add client code content to zip + src_root = os.path.normpath(str(addon_output_dir.absolute())) + src_root_offset = len(src_root) + 1 + for root, _, filenames in os.walk(str(addon_output_dir)): + rel_root = "" + if root != src_root: + rel_root = root[src_root_offset:] + + for filename in filenames: + src_path = os.path.join(root, filename) + if rel_root: + dst_path = os.path.join("addon", rel_root, filename) + else: + dst_path = os.path.join("addon", filename) + zipf.write(src_path, dst_path) + + if not keep_source: + shutil.rmtree(str(output_dir / addon_name)) + + +def create_openpype_package( + addon_dir: Path, + output_dir: Path, + root_dir: Path, + create_zip: bool, + keep_source: bool +): + server_dir = addon_dir / "server" + pyproject_path = addon_dir / "client" / "pyproject.toml" + + openpype_dir = root_dir / "openpype" + version_path = openpype_dir / "version.py" + addon_version = read_addon_version(version_path) + + addon_output_dir = output_dir / "openpype" / addon_version + private_dir = addon_output_dir / "private" + # Make sure dir exists + addon_output_dir.mkdir(parents=True) + private_dir.mkdir(parents=True) + + # Copy version + shutil.copy(str(version_path), str(addon_output_dir)) + for subitem in server_dir.iterdir(): + shutil.copy(str(subitem), str(addon_output_dir / subitem.name)) + + # Copy pyproject.toml + shutil.copy( + str(pyproject_path), + (private_dir / pyproject_path.name) + ) + + # Zip client + zip_filepath = private_dir / "client.zip" + with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: + # Add client code content to zip + for path, sub_path in find_files_in_subdir(str(openpype_dir)): + zipf.write(path, f"{openpype_dir.name}/{sub_path}") + + if create_zip: + create_addon_zip(output_dir, "openpype", addon_version, keep_source) + + +def create_addon_package( + addon_dir: Path, + output_dir: Path, + create_zip: bool, + keep_source: bool +): + server_dir = addon_dir / "server" + addon_version = get_addon_version(addon_dir) + + addon_output_dir = output_dir / addon_dir.name / addon_version + if addon_output_dir.exists(): + shutil.rmtree(str(addon_output_dir)) + addon_output_dir.mkdir(parents=True) + + # Copy server content + src_root = os.path.normpath(str(server_dir.absolute())) + src_root_offset = len(src_root) + 1 + for root, _, filenames in os.walk(str(server_dir)): + dst_root = addon_output_dir + if root != src_root: + rel_root = root[src_root_offset:] + dst_root = dst_root / rel_root + + dst_root.mkdir(parents=True, exist_ok=True) + for filename in filenames: + src_path = os.path.join(root, filename) + shutil.copy(src_path, str(dst_root)) + + if create_zip: + create_addon_zip( + output_dir, addon_dir.name, addon_version, keep_source + ) + + +def main(create_zip=True, keep_source=False): + current_dir = Path(os.path.dirname(os.path.abspath(__file__))) + root_dir = current_dir.parent + output_dir = current_dir / "packages" + print("Package creation started...") + + # Make sure package dir is empty + if output_dir.exists(): + shutil.rmtree(str(output_dir)) + # Make sure output dir is created + output_dir.mkdir(parents=True) + + for addon_dir in current_dir.iterdir(): + if not addon_dir.is_dir(): + continue + + server_dir = addon_dir / "server" + if not server_dir.exists(): + continue + + if addon_dir.name == "openpype": + create_openpype_package( + addon_dir, output_dir, root_dir, create_zip, keep_source + ) + + else: + create_addon_package( + addon_dir, output_dir, create_zip, keep_source + ) + + print(f"- package '{addon_dir.name}' created") + print(f"Package creation finished. Output directory: {output_dir}") + + +if __name__ == "__main__": + create_zip = "--skip-zip" not in sys.argv + keep_sources = "--keep-sources" in sys.argv + main(create_zip, keep_sources) diff --git a/server_addon/deadline/server/__init__.py b/server_addon/deadline/server/__init__.py new file mode 100644 index 0000000000..36d04189a9 --- /dev/null +++ b/server_addon/deadline/server/__init__.py @@ -0,0 +1,17 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import DeadlineSettings, DEFAULT_VALUES + + +class Deadline(BaseServerAddon): + name = "deadline" + title = "Deadline" + version = __version__ + settings_model: Type[DeadlineSettings] = DeadlineSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/deadline/server/settings/__init__.py b/server_addon/deadline/server/settings/__init__.py new file mode 100644 index 0000000000..0307862afa --- /dev/null +++ b/server_addon/deadline/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + DeadlineSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "DeadlineSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py new file mode 100644 index 0000000000..e60df2eda3 --- /dev/null +++ b/server_addon/deadline/server/settings/main.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator + +from ayon_server.settings import BaseSettingsModel, ensure_unique_names + +from .publish_plugins import ( + PublishPluginsModel, + DEFAULT_DEADLINE_PLUGINS_SETTINGS +) + + +class ServerListSubmodel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: str = Field(title="Value") + + +class DeadlineSettings(BaseSettingsModel): + deadline_urls: list[ServerListSubmodel] = Field( + default_factory=list, + title="System Deadline Webservice URLs", + ) + + deadline_servers: list[str] = Field( + title="Project deadline servers", + section="---") + + publish: PublishPluginsModel = Field( + default_factory=PublishPluginsModel, + title="Publish Plugins", + ) + + @validator("deadline_urls") + def validate_unique_names(cls, value): + ensure_unique_names(value) + return value + + +DEFAULT_VALUES = { + "deadline_urls": [ + { + "name": "default", + "value": "http://127.0.0.1:8082" + } + ], + # TODO: this needs to be dynamic from "deadline_urls" + "deadline_servers": [], + "publish": DEFAULT_DEADLINE_PLUGINS_SETTINGS +} diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py new file mode 100644 index 0000000000..8d1b667345 --- /dev/null +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -0,0 +1,435 @@ +from pydantic import Field, validator + +from ayon_server.settings import BaseSettingsModel, ensure_unique_names + + +class CollectDefaultDeadlineServerModel(BaseSettingsModel): + """Settings for event handlers running in ftrack service.""" + + pass_mongo_url: bool = Field(title="Pass Mongo url to job") + + +class CollectDeadlinePoolsModel(BaseSettingsModel): + """Settings Deadline default pools.""" + + primary_pool: str = Field(title="Primary Pool") + + secondary_pool: str = Field(title="Secondary Pool") + + +class ValidateExpectedFilesModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + active: bool = Field(True, title="Active") + allow_user_override: bool = Field( + True, title="Allow user change frame range" + ) + families: list[str] = Field( + default_factory=list, title="Trigger on families" + ) + targets: list[str] = Field( + default_factory=list, title="Trigger for plugins" + ) + + +def tile_assembler_enum(): + """Return a list of value/label dicts for the enumerator. + + Returning a list of dicts is used to allow for a custom label to be + displayed in the UI. + """ + return [ + { + "value": "DraftTileAssembler", + "label": "Draft Tile Assembler" + }, + { + "value": "OpenPypeTileAssembler", + "label": "Open Image IO" + } + ] + + +class ScenePatchesSubmodel(BaseSettingsModel): + _layout = "expanded" + name: str = Field(title="Patch name") + regex: str = Field(title="Patch regex") + line: str = Field(title="Patch line") + + +class MayaSubmitDeadlineModel(BaseSettingsModel): + """Maya deadline submitter settings.""" + + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + use_published: bool = Field(title="Use Published scene") + import_reference: bool = Field(title="Use Scene with Imported Reference") + asset_dependencies: bool = Field(title="Use Asset dependencies") + priority: int = Field(title="Priority") + tile_priority: int = Field(title="Tile Priority") + group: str = Field(title="Group") + limit: list[str] = Field( + default_factory=list, + title="Limit Groups" + ) + tile_assembler_plugin: str = Field( + title="Tile Assembler Plugin", + enum_resolver=tile_assembler_enum, + ) + jobInfo: str = Field( + title="Additional JobInfo data", + widget="textarea", + ) + pluginInfo: str = Field( + title="Additional PluginInfo data", + widget="textarea", + ) + + scene_patches: list[ScenePatchesSubmodel] = Field( + default_factory=list, + title="Scene patches", + ) + strict_error_checking: bool = Field( + title="Disable Strict Error Check profiles" + ) + + @validator("limit", "scene_patches") + def validate_unique_names(cls, value): + ensure_unique_names(value) + return value + + +class MaxSubmitDeadlineModel(BaseSettingsModel): + enabled: bool = Field(True) + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + use_published: bool = Field(title="Use Published scene") + priority: int = Field(title="Priority") + chunk_size: int = Field(title="Frame per Task") + group: str = Field("", title="Group Name") + + +class EnvSearchReplaceSubmodel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: str = Field(title="Value") + + +class LimitGroupsSubmodel(BaseSettingsModel): + _layout = "expanded" + name: str = Field(title="Name") + value: list[str] = Field( + default_factory=list, + title="Limit Groups" + ) + + +class FusionSubmitDeadlineModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + priority: int = Field(50, title="Priority") + chunk_size: int = Field(10, title="Frame per Task") + concurrent_tasks: int = Field(1, title="Number of concurrent tasks") + group: str = Field("", title="Group Name") + + +class NukeSubmitDeadlineModel(BaseSettingsModel): + """Nuke deadline submitter settings.""" + + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + priority: int = Field(title="Priority") + chunk_size: int = Field(title="Chunk Size") + concurrent_tasks: int = Field(title="Number of concurrent tasks") + group: str = Field(title="Group") + department: str = Field(title="Department") + use_gpu: bool = Field(title="Use GPU") + + env_allowed_keys: list[str] = Field( + default_factory=list, + title="Allowed environment keys" + ) + + env_search_replace_values: list[EnvSearchReplaceSubmodel] = Field( + default_factory=list, + title="Search & replace in environment values", + ) + + limit_groups: list[LimitGroupsSubmodel] = Field( + default_factory=list, + title="Limit Groups", + ) + + @validator("limit_groups", "env_allowed_keys", "env_search_replace_values") + def validate_unique_names(cls, value): + ensure_unique_names(value) + return value + + +class HarmonySubmitDeadlineModel(BaseSettingsModel): + """Harmony deadline submitter settings.""" + + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + use_published: bool = Field(title="Use Published scene") + priority: int = Field(title="Priority") + chunk_size: int = Field(title="Chunk Size") + group: str = Field(title="Group") + department: str = Field(title="Department") + + +class AfterEffectsSubmitDeadlineModel(BaseSettingsModel): + """After Effects deadline submitter settings.""" + + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + use_published: bool = Field(title="Use Published scene") + priority: int = Field(title="Priority") + chunk_size: int = Field(title="Chunk Size") + group: str = Field(title="Group") + department: str = Field(title="Department") + multiprocess: bool = Field(title="Optional") + + +class CelactionSubmitDeadlineModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + deadline_department: str = Field("", title="Deadline apartment") + deadline_priority: int = Field(50, title="Deadline priority") + deadline_pool: str = Field("", title="Deadline pool") + deadline_pool_secondary: str = Field("", title="Deadline pool (secondary)") + deadline_group: str = Field("", title="Deadline Group") + deadline_chunk_size: int = Field(10, title="Deadline Chunk size") + deadline_job_delay: str = Field( + "", title="Delay job (timecode dd:hh:mm:ss)" + ) + + +class AOVFilterSubmodel(BaseSettingsModel): + _layout = "expanded" + name: str = Field(title="Host") + value: list[str] = Field( + default_factory=list, + title="AOV regex" + ) + + +class ProcessSubmittedJobOnFarmModel(BaseSettingsModel): + """Process submitted job on farm.""" + + enabled: bool = Field(title="Enabled") + deadline_department: str = Field(title="Department") + deadline_pool: str = Field(title="Pool") + deadline_group: str = Field(title="Group") + deadline_chunk_size: int = Field(title="Chunk Size") + deadline_priority: int = Field(title="Priority") + publishing_script: str = Field(title="Publishing script path") + skip_integration_repre_list: list[str] = Field( + default_factory=list, + title="Skip integration of representation with ext" + ) + aov_filter: list[AOVFilterSubmodel] = Field( + default_factory=list, + title="Reviewable products filter", + ) + + @validator("aov_filter", "skip_integration_repre_list") + def validate_unique_names(cls, value): + ensure_unique_names(value) + return value + + +class PublishPluginsModel(BaseSettingsModel): + CollectDefaultDeadlineServer: CollectDefaultDeadlineServerModel = Field( + default_factory=CollectDefaultDeadlineServerModel, + title="Default Deadline Webservice") + CollectDefaultDeadlineServer: CollectDefaultDeadlineServerModel = Field( + default_factory=CollectDefaultDeadlineServerModel, + title="Default Deadline Webservice") + CollectDeadlinePools: CollectDeadlinePoolsModel = Field( + default_factory=CollectDeadlinePoolsModel, + title="Default Pools") + ValidateExpectedFiles: ValidateExpectedFilesModel = Field( + default_factory=ValidateExpectedFilesModel, + title="Validate Expected Files" + ) + MayaSubmitDeadline: MayaSubmitDeadlineModel = Field( + default_factory=MayaSubmitDeadlineModel, + title="Maya Submit to deadline") + MaxSubmitDeadline: MaxSubmitDeadlineModel = Field( + default_factory=MaxSubmitDeadlineModel, + title="Max Submit to deadline") + FusionSubmitDeadline: FusionSubmitDeadlineModel = Field( + default_factory=FusionSubmitDeadlineModel, + title="Fusion submit to Deadline") + NukeSubmitDeadline: NukeSubmitDeadlineModel = Field( + default_factory=NukeSubmitDeadlineModel, + title="Nuke Submit to deadline") + HarmonySubmitDeadline: HarmonySubmitDeadlineModel = Field( + default_factory=HarmonySubmitDeadlineModel, + title="Harmony Submit to deadline") + AfterEffectsSubmitDeadline: AfterEffectsSubmitDeadlineModel = Field( + default_factory=AfterEffectsSubmitDeadlineModel, + title="After Effects to deadline") + CelactionSubmitDeadline: CelactionSubmitDeadlineModel = Field( + default_factory=CelactionSubmitDeadlineModel, + title="Celaction Submit Deadline" + ) + ProcessSubmittedJobOnFarm: ProcessSubmittedJobOnFarmModel = Field( + default_factory=ProcessSubmittedJobOnFarmModel, + title="Process submitted job on farm.") + + +DEFAULT_DEADLINE_PLUGINS_SETTINGS = { + "CollectDefaultDeadlineServer": { + "pass_mongo_url": True + }, + "CollectDeadlinePools": { + "primary_pool": "", + "secondary_pool": "" + }, + "ValidateExpectedFiles": { + "enabled": True, + "active": True, + "allow_user_override": True, + "families": [ + "render" + ], + "targets": [ + "deadline" + ] + }, + "MayaSubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "tile_assembler_plugin": "DraftTileAssembler", + "use_published": True, + "import_reference": False, + "asset_dependencies": True, + "strict_error_checking": True, + "priority": 50, + "tile_priority": 50, + "group": "none", + "limit": [], + # this used to be empty dict + "jobInfo": "", + # this used to be empty dict + "pluginInfo": "", + "scene_patches": [] + }, + "MaxSubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "use_published": True, + "priority": 50, + "chunk_size": 10, + "group": "none" + }, + "FusionSubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "priority": 50, + "chunk_size": 10, + "concurrent_tasks": 1, + "group": "" + }, + "NukeSubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "priority": 50, + "chunk_size": 10, + "concurrent_tasks": 1, + "group": "", + "department": "", + "use_gpu": True, + "env_allowed_keys": [], + "env_search_replace_values": [], + "limit_groups": [] + }, + "HarmonySubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "use_published": True, + "priority": 50, + "chunk_size": 10000, + "group": "", + "department": "" + }, + "AfterEffectsSubmitDeadline": { + "enabled": True, + "optional": False, + "active": True, + "use_published": True, + "priority": 50, + "chunk_size": 10000, + "group": "", + "department": "", + "multiprocess": True + }, + "CelactionSubmitDeadline": { + "enabled": True, + "deadline_department": "", + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_group": "", + "deadline_chunk_size": 10, + "deadline_job_delay": "00:00:00:00" + }, + "ProcessSubmittedJobOnFarm": { + "enabled": True, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "", + "deadline_chunk_size": 1, + "deadline_priority": 50, + "publishing_script": "", + "skip_integration_repre_list": [], + "aov_filter": [ + { + "name": "maya", + "value": [ + ".*([Bb]eauty).*" + ] + }, + { + "name": "aftereffects", + "value": [ + ".*" + ] + }, + { + "name": "celaction", + "value": [ + ".*" + ] + }, + { + "name": "harmony", + "value": [ + ".*" + ] + }, + { + "name": "max", + "value": [ + ".*" + ] + }, + { + "name": "fusion", + "value": [ + ".*" + ] + } + ] + } +} diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/deadline/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/flame/server/__init__.py b/server_addon/flame/server/__init__.py new file mode 100644 index 0000000000..7d5eb3960f --- /dev/null +++ b/server_addon/flame/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import FlameSettings, DEFAULT_VALUES + + +class FlameAddon(BaseServerAddon): + name = "flame" + title = "Flame" + version = __version__ + settings_model: Type[FlameSettings] = FlameSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/flame/server/settings/__init__.py b/server_addon/flame/server/settings/__init__.py new file mode 100644 index 0000000000..39b8220d40 --- /dev/null +++ b/server_addon/flame/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + FlameSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "FlameSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/flame/server/settings/create_plugins.py b/server_addon/flame/server/settings/create_plugins.py new file mode 100644 index 0000000000..374a7368d2 --- /dev/null +++ b/server_addon/flame/server/settings/create_plugins.py @@ -0,0 +1,120 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class CreateShotClipModel(BaseSettingsModel): + hierarchy: str = Field( + "shot", + title="Shot parent hierarchy", + section="Shot Hierarchy And Rename Settings" + ) + useShotName: bool = Field( + True, + title="Use Shot Name", + ) + clipRename: bool = Field( + False, + title="Rename clips", + ) + clipName: str = Field( + "{sequence}{shot}", + title="Clip name template" + ) + segmentIndex: bool = Field( + True, + title="Accept segment order" + ) + countFrom: int = Field( + 10, + title="Count sequence from" + ) + countSteps: int = Field( + 10, + title="Stepping number" + ) + + folder: str = Field( + "shots", + title="{folder}", + section="Shot Template Keywords" + ) + episode: str = Field( + "ep01", + title="{episode}" + ) + sequence: str = Field( + "a", + title="{sequence}" + ) + track: str = Field( + "{_track_}", + title="{track}" + ) + shot: str = Field( + "####", + title="{shot}" + ) + + vSyncOn: bool = Field( + False, + title="Enable Vertical Sync", + section="Vertical Synchronization Of Attributes" + ) + + workfileFrameStart: int = Field( + 1001, + title="Workfiles Start Frame", + section="Shot Attributes" + ) + handleStart: int = Field( + 10, + title="Handle start (head)" + ) + handleEnd: int = Field( + 10, + title="Handle end (tail)" + ) + includeHandles: bool = Field( + False, + title="Enable handles including" + ) + retimedHandles: bool = Field( + True, + title="Enable retimed handles" + ) + retimedFramerange: bool = Field( + True, + title="Enable retimed shot frameranges" + ) + + +class CreatePuginsModel(BaseSettingsModel): + CreateShotClip: CreateShotClipModel = Field( + default_factory=CreateShotClipModel, + title="Create Shot Clip" + ) + + +DEFAULT_CREATE_SETTINGS = { + "CreateShotClip": { + "hierarchy": "{folder}/{sequence}", + "useShotName": True, + "clipRename": False, + "clipName": "{sequence}{shot}", + "segmentIndex": True, + "countFrom": 10, + "countSteps": 10, + "folder": "shots", + "episode": "ep01", + "sequence": "a", + "track": "{_track_}", + "shot": "####", + "vSyncOn": False, + "workfileFrameStart": 1001, + "handleStart": 5, + "handleEnd": 5, + "includeHandles": False, + "retimedHandles": True, + "retimedFramerange": True + } +} diff --git a/server_addon/flame/server/settings/imageio.py b/server_addon/flame/server/settings/imageio.py new file mode 100644 index 0000000000..ef1e4721d1 --- /dev/null +++ b/server_addon/flame/server/settings/imageio.py @@ -0,0 +1,130 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel, ensure_unique_names + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ImageIORemappingRulesModel(BaseSettingsModel): + host_native_name: str = Field( + title="Application native colorspace name" + ) + ocio_name: str = Field(title="OCIO colorspace name") + + +class ImageIORemappingModel(BaseSettingsModel): + rules: list[ImageIORemappingRulesModel] = Field( + default_factory=list + ) + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ProfileNamesMappingInputsModel(BaseSettingsModel): + _layout = "expanded" + + flameName: str = Field("", title="Flame name") + ocioName: str = Field("", title="OCIO name") + + +class ProfileNamesMappingModel(BaseSettingsModel): + _layout = "expanded" + + inputs: list[ProfileNamesMappingInputsModel] = Field( + default_factory=list, + title="Profile names mapping" + ) + + +class ImageIOProjectModel(BaseSettingsModel): + colourPolicy: str = Field( + "ACES 1.1", + title="Colour Policy (name or path)", + section="Project" + ) + frameDepth: str = Field( + "16-bit fp", + title="Image Depth" + ) + fieldDominance: str = Field( + "PROGRESSIVE", + title="Field Dominance" + ) + + +class FlameImageIOModel(BaseSettingsModel): + _isGroup = True + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + remapping: ImageIORemappingModel = Field( + title="Remapping colorspace names", + default_factory=ImageIORemappingModel + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) + # NOTE 'project' attribute was expanded to this model but that caused + # inconsistency with v3 settings and harder conversion handling + # - it can be moved back but keep in mind that it must be handled in v3 + # conversion script too + project: ImageIOProjectModel = Field( + default_factory=ImageIOProjectModel, + title="Project" + ) + profilesMapping: ProfileNamesMappingModel = Field( + default_factory=ProfileNamesMappingModel, + title="Profile names mapping" + ) + + +DEFAULT_IMAGEIO_SETTINGS = { + "project": { + "colourPolicy": "ACES 1.1", + "frameDepth": "16-bit fp", + "fieldDominance": "PROGRESSIVE" + }, + "profilesMapping": { + "inputs": [ + { + "flameName": "ACEScg", + "ocioName": "ACES - ACEScg" + }, + { + "flameName": "Rec.709 video", + "ocioName": "Output - Rec.709" + } + ] + } +} diff --git a/server_addon/flame/server/settings/loader_plugins.py b/server_addon/flame/server/settings/loader_plugins.py new file mode 100644 index 0000000000..6c27b926c2 --- /dev/null +++ b/server_addon/flame/server/settings/loader_plugins.py @@ -0,0 +1,99 @@ +from ayon_server.settings import Field, BaseSettingsModel + + +class LoadClipModel(BaseSettingsModel): + enabled: bool = Field(True) + + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + reel_group_name: str = Field( + "OpenPype_Reels", + title="Reel group name" + ) + reel_name: str = Field( + "Loaded", + title="Reel name" + ) + + clip_name_template: str = Field( + "{folder[name]}_{product[name]}<_{output}>", + title="Clip name template" + ) + layer_rename_template: str = Field("", title="Layer name template") + layer_rename_patterns: list[str] = Field( + default_factory=list, + title="Layer rename patters", + ) + + +class LoadClipBatchModel(BaseSettingsModel): + enabled: bool = Field(True) + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + reel_name: str = Field( + "OP_LoadedReel", + title="Reel name" + ) + clip_name_template: str = Field( + "{batch}_{folder[name]}_{product[name]}<_{output}>", + title="Clip name template" + ) + layer_rename_template: str = Field("", title="Layer name template") + layer_rename_patterns: list[str] = Field( + default_factory=list, + title="Layer rename patters", + ) + + +class LoaderPluginsModel(BaseSettingsModel): + LoadClip: LoadClipModel = Field( + default_factory=LoadClipModel, + title="Load Clip" + ) + LoadClipBatch: LoadClipBatchModel = Field( + default_factory=LoadClipBatchModel, + title="Load as clip to current batch" + ) + + +DEFAULT_LOADER_SETTINGS = { + "LoadClip": { + "enabled": True, + "product_types": [ + "render2d", + "source", + "plate", + "render", + "review" + ], + "reel_group_name": "OpenPype_Reels", + "reel_name": "Loaded", + "clip_name_template": "{folder[name]}_{product[name]}<_{output}>", + "layer_rename_template": "{folder[name]}_{product[name]}<_{output}>", + "layer_rename_patterns": [ + "rgb", + "rgba" + ] + }, + "LoadClipBatch": { + "enabled": True, + "product_types": [ + "render2d", + "source", + "plate", + "render", + "review" + ], + "reel_name": "OP_LoadedReel", + "clip_name_template": "{batch}_{folder[name]}_{product[name]}<_{output}>", + "layer_rename_template": "{folder[name]}_{product[name]}<_{output}>", + "layer_rename_patterns": [ + "rgb", + "rgba" + ] + } +} diff --git a/server_addon/flame/server/settings/main.py b/server_addon/flame/server/settings/main.py new file mode 100644 index 0000000000..f28de6641b --- /dev/null +++ b/server_addon/flame/server/settings/main.py @@ -0,0 +1,33 @@ +from ayon_server.settings import Field, BaseSettingsModel + +from .imageio import FlameImageIOModel, DEFAULT_IMAGEIO_SETTINGS +from .create_plugins import CreatePuginsModel, DEFAULT_CREATE_SETTINGS +from .publish_plugins import PublishPuginsModel, DEFAULT_PUBLISH_SETTINGS +from .loader_plugins import LoaderPluginsModel, DEFAULT_LOADER_SETTINGS + + +class FlameSettings(BaseSettingsModel): + imageio: FlameImageIOModel = Field( + default_factory=FlameImageIOModel, + title="Color Management (ImageIO)" + ) + create: CreatePuginsModel = Field( + default_factory=CreatePuginsModel, + title="Create plugins" + ) + publish: PublishPuginsModel = Field( + default_factory=PublishPuginsModel, + title="Publish plugins" + ) + load: LoaderPluginsModel = Field( + default_factory=LoaderPluginsModel, + title="Loader plugins" + ) + + +DEFAULT_VALUES = { + "imageio": DEFAULT_IMAGEIO_SETTINGS, + "create": DEFAULT_CREATE_SETTINGS, + "publish": DEFAULT_PUBLISH_SETTINGS, + "load": DEFAULT_LOADER_SETTINGS +} diff --git a/server_addon/flame/server/settings/publish_plugins.py b/server_addon/flame/server/settings/publish_plugins.py new file mode 100644 index 0000000000..ea7f109f73 --- /dev/null +++ b/server_addon/flame/server/settings/publish_plugins.py @@ -0,0 +1,190 @@ +from ayon_server.settings import Field, BaseSettingsModel, task_types_enum + + +class XMLPresetAttrsFromCommentsModel(BaseSettingsModel): + _layout = "expanded" + name: str = Field("", title="Attribute name") + type: str = Field( + default_factory=str, + title="Attribute type", + enum_resolver=lambda: ["number", "float", "string"] + ) + + +class AddTasksModel(BaseSettingsModel): + _layout = "expanded" + name: str = Field("", title="Task name") + type: str = Field( + default_factory=str, + title="Task type", + enum_resolver=task_types_enum + ) + create_batch_group: bool = Field( + True, + title="Create batch group" + ) + + +class CollectTimelineInstancesModel(BaseSettingsModel): + _isGroup = True + + xml_preset_attrs_from_comments: list[XMLPresetAttrsFromCommentsModel] = Field( + default_factory=list, + title="XML presets attributes parsable from segment comments" + ) + add_tasks: list[AddTasksModel] = Field( + default_factory=list, + title="Add tasks" + ) + + +class ExportPresetsMappingModel(BaseSettingsModel): + _layout = "expanded" + + name: str = Field( + ..., + title="Name" + ) + active: bool = Field(True, title="Is active") + export_type: str = Field( + "File Sequence", + title="Eport clip type", + enum_resolver=lambda: ["Movie", "File Sequence", "Sequence Publish"] + ) + ext: str = Field("exr", title="Output extension") + xml_preset_file: str = Field( + "OpenEXR (16-bit fp DWAA).xml", + title="XML preset file (with ext)" + ) + colorspace_out: str = Field( + "ACES - ACEScg", + title="Output color (imageio)" + ) + # TODO remove when resolved or v3 is not a thing anymore + # NOTE next 4 attributes were grouped under 'other_parameters' but that + # created inconsistency with v3 settings and harder conversion handling + # - it can be moved back but keep in mind that it must be handled in v3 + # conversion script too + xml_preset_dir: str = Field( + "", + title="XML preset directory" + ) + parsed_comment_attrs: bool = Field( + True, + title="Parsed comment attributes" + ) + representation_add_range: bool = Field( + True, + title="Add range to representation name" + ) + representation_tags: list[str] = Field( + default_factory=list, + title="Representation tags" + ) + load_to_batch_group: bool = Field( + True, + title="Load to batch group reel" + ) + batch_group_loader_name: str = Field( + "LoadClipBatch", + title="Use loader name" + ) + filter_path_regex: str = Field( + ".*", + title="Regex in clip path" + ) + + +class ExtractProductResourcesModel(BaseSettingsModel): + _isGroup = True + + keep_original_representation: bool = Field( + False, + title="Publish clip's original media" + ) + export_presets_mapping: list[ExportPresetsMappingModel] = Field( + default_factory=list, + title="Export presets mapping" + ) + + +class IntegrateBatchGroupModel(BaseSettingsModel): + enabled: bool = Field( + False, + title="Enabled" + ) + + +class PublishPuginsModel(BaseSettingsModel): + CollectTimelineInstances: CollectTimelineInstancesModel = Field( + default_factory=CollectTimelineInstancesModel, + title="Collect Timeline Instances" + ) + + ExtractProductResources: ExtractProductResourcesModel = Field( + default_factory=ExtractProductResourcesModel, + title="Extract Product Resources" + ) + + IntegrateBatchGroup: IntegrateBatchGroupModel = Field( + default_factory=IntegrateBatchGroupModel, + title="IntegrateBatchGroup" + ) + + +DEFAULT_PUBLISH_SETTINGS = { + "CollectTimelineInstances": { + "xml_preset_attrs_from_comments": [ + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "pixelRatio", + "type": "float" + }, + { + "name": "resizeType", + "type": "string" + }, + { + "name": "resizeFilter", + "type": "string" + } + ], + "add_tasks": [ + { + "name": "compositing", + "type": "Compositing", + "create_batch_group": True + } + ] + }, + "ExtractProductResources": { + "keep_original_representation": False, + "export_presets_mapping": [ + { + "name": "exr16fpdwaa", + "active": True, + "export_type": "File Sequence", + "ext": "exr", + "xml_preset_file": "OpenEXR (16-bit fp DWAA).xml", + "colorspace_out": "ACES - ACEScg", + "xml_preset_dir": "", + "parsed_comment_attrs": True, + "representation_add_range": True, + "representation_tags": [], + "load_to_batch_group": True, + "batch_group_loader_name": "LoadClipBatch", + "filter_path_regex": ".*" + } + ] + }, + "IntegrateBatchGroup": { + "enabled": False + } +} diff --git a/server_addon/flame/server/version.py b/server_addon/flame/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/flame/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/fusion/server/__init__.py b/server_addon/fusion/server/__init__.py new file mode 100644 index 0000000000..4d43f28812 --- /dev/null +++ b/server_addon/fusion/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import FusionSettings, DEFAULT_VALUES + + +class FusionAddon(BaseServerAddon): + name = "fusion" + title = "Fusion" + version = __version__ + settings_model: Type[FusionSettings] = FusionSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/fusion/server/imageio.py b/server_addon/fusion/server/imageio.py new file mode 100644 index 0000000000..fe867af424 --- /dev/null +++ b/server_addon/fusion/server/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class FusionImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/fusion/server/settings.py b/server_addon/fusion/server/settings.py new file mode 100644 index 0000000000..92fb362c66 --- /dev/null +++ b/server_addon/fusion/server/settings.py @@ -0,0 +1,95 @@ +from pydantic import Field +from ayon_server.settings import ( + BaseSettingsModel, +) + +from .imageio import FusionImageIOModel + + +class CopyFusionSettingsModel(BaseSettingsModel): + copy_path: str = Field("", title="Local Fusion profile directory") + copy_status: bool = Field(title="Copy profile on first launch") + force_sync: bool = Field(title="Resync profile on each launch") + + +def _create_saver_instance_attributes_enum(): + return [ + { + "value": "reviewable", + "label": "Reviewable" + }, + { + "value": "farm_rendering", + "label": "Farm rendering" + } + ] + + +class CreateSaverPluginModel(BaseSettingsModel): + _isGroup = True + temp_rendering_path_template: str = Field( + "", title="Temporary rendering path template" + ) + default_variants: list[str] = Field( + default_factory=list, + title="Default variants" + ) + instance_attributes: list[str] = Field( + default_factory=list, + enum_resolver=_create_saver_instance_attributes_enum, + title="Instance attributes" + ) + + +class CreatPluginsModel(BaseSettingsModel): + CreateSaver: CreateSaverPluginModel = Field( + default_factory=CreateSaverPluginModel, + title="Create Saver" + ) + + +class FusionSettings(BaseSettingsModel): + imageio: FusionImageIOModel = Field( + default_factory=FusionImageIOModel, + title="Color Management (ImageIO)" + ) + copy_fusion_settings: CopyFusionSettingsModel = Field( + default_factory=CopyFusionSettingsModel, + title="Local Fusion profile settings" + ) + create: CreatPluginsModel = Field( + default_factory=CreatPluginsModel, + title="Creator plugins" + ) + + +DEFAULT_VALUES = { + "imageio": { + "ocio_config": { + "enabled": False, + "filepath": [] + }, + "file_rules": { + "enabled": False, + "rules": [] + } + }, + "copy_fusion_settings": { + "copy_path": "~/.openpype/hosts/fusion/profiles", + "copy_status": False, + "force_sync": False + }, + "create": { + "CreateSaver": { + "temp_rendering_path_template": "{workdir}/renders/fusion/{product[name]}/{product[name]}.{frame}.{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ] + } + } +} diff --git a/server_addon/fusion/server/version.py b/server_addon/fusion/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/fusion/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/harmony/LICENSE b/server_addon/harmony/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/server_addon/harmony/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/server_addon/harmony/README.md b/server_addon/harmony/README.md new file mode 100644 index 0000000000..d971fa39f9 --- /dev/null +++ b/server_addon/harmony/README.md @@ -0,0 +1,4 @@ +ToonBoom Harmony Addon +=============== + +Integration with ToonBoom Harmony. diff --git a/server_addon/harmony/server/__init__.py b/server_addon/harmony/server/__init__.py new file mode 100644 index 0000000000..64f41849ad --- /dev/null +++ b/server_addon/harmony/server/__init__.py @@ -0,0 +1,15 @@ +from ayon_server.addons import BaseServerAddon + +from .settings import HarmonySettings, DEFAULT_HARMONY_SETTING +from .version import __version__ + + +class Harmony(BaseServerAddon): + name = "harmony" + version = __version__ + + settings_model = HarmonySettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_HARMONY_SETTING) diff --git a/server_addon/harmony/server/settings/__init__.py b/server_addon/harmony/server/settings/__init__.py new file mode 100644 index 0000000000..4a8118d4da --- /dev/null +++ b/server_addon/harmony/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + HarmonySettings, + DEFAULT_HARMONY_SETTING, +) + + +__all__ = ( + "HarmonySettings", + "DEFAULT_HARMONY_SETTING", +) diff --git a/server_addon/harmony/server/settings/imageio.py b/server_addon/harmony/server/settings/imageio.py new file mode 100644 index 0000000000..4e01fae3d4 --- /dev/null +++ b/server_addon/harmony/server/settings/imageio.py @@ -0,0 +1,55 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ImageIORemappingRulesModel(BaseSettingsModel): + host_native_name: str = Field( + title="Application native colorspace name" + ) + ocio_name: str = Field(title="OCIO colorspace name") + + +class HarmonyImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/harmony/server/settings/load.py b/server_addon/harmony/server/settings/load.py new file mode 100644 index 0000000000..1222485ff9 --- /dev/null +++ b/server_addon/harmony/server/settings/load.py @@ -0,0 +1,20 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class ImageSequenceLoaderModel(BaseSettingsModel): + family: list[str] = Field( + default_factory=list, + title="Families" + ) + representations: list[str] = Field( + default_factory=list, + title="Representations" + ) + + +class HarmonyLoadModel(BaseSettingsModel): + ImageSequenceLoader: ImageSequenceLoaderModel = Field( + default_factory=ImageSequenceLoaderModel, + title="Load Image Sequence" + ) diff --git a/server_addon/harmony/server/settings/main.py b/server_addon/harmony/server/settings/main.py new file mode 100644 index 0000000000..ae08da0198 --- /dev/null +++ b/server_addon/harmony/server/settings/main.py @@ -0,0 +1,68 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + +from .imageio import HarmonyImageIOModel +from .load import HarmonyLoadModel +from .publish_plugins import HarmonyPublishPlugins + + +class HarmonySettings(BaseSettingsModel): + """Harmony Project Settings.""" + + imageio: HarmonyImageIOModel = Field( + default_factory=HarmonyImageIOModel, + title="OCIO config" + ) + load: HarmonyLoadModel = Field( + default_factory=HarmonyLoadModel, + title="Loader plugins" + ) + publish: HarmonyPublishPlugins = Field( + default_factory=HarmonyPublishPlugins, + title="Publish plugins" + ) + + +DEFAULT_HARMONY_SETTING = { + "load": { + "ImageSequenceLoader": { + "family": [ + "shot", + "render", + "image", + "plate", + "reference" + ], + "representations": [ + "jpeg", + "png", + "jpg" + ] + } + }, + "publish": { + "CollectPalettes": { + "allowed_tasks": [ + ".*" + ] + }, + "ValidateAudio": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateContainers": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateSceneSettings": { + "enabled": True, + "optional": True, + "active": True, + "frame_check_filter": [], + "skip_resolution_check": [], + "skip_timelines_check": [] + } + } +} diff --git a/server_addon/harmony/server/settings/publish_plugins.py b/server_addon/harmony/server/settings/publish_plugins.py new file mode 100644 index 0000000000..bdaec2bbd4 --- /dev/null +++ b/server_addon/harmony/server/settings/publish_plugins.py @@ -0,0 +1,76 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class CollectPalettesPlugin(BaseSettingsModel): + """Set regular expressions to filter triggering on specific task names. '.*' means on all.""" # noqa + + allowed_tasks: list[str] = Field( + default_factory=list, + title="Allowed tasks" + ) + + +class ValidateAudioPlugin(BaseSettingsModel): + """Check if scene contains audio track.""" # + _isGroup = True + enabled: bool = True + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + + +class ValidateContainersPlugin(BaseSettingsModel): + """Check if loaded container is scene are latest versions.""" + _isGroup = True + enabled: bool = True + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + + +class ValidateSceneSettingsPlugin(BaseSettingsModel): + """Validate if FrameStart, FrameEnd and Resolution match shot data in DB. + Use regular expressions to limit validations only on particular asset + or task names.""" + _isGroup = True + enabled: bool = True + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + + frame_check_filter: list[str] = Field( + default_factory=list, + title="Skip Frame check for Assets with name containing" + ) + + skip_resolution_check: list[str] = Field( + default_factory=list, + title="Skip Resolution Check for Tasks" + ) + + skip_timelines_check: list[str] = Field( + default_factory=list, + title="Skip Timeline Check for Tasks" + ) + + +class HarmonyPublishPlugins(BaseSettingsModel): + + CollectPalettes: CollectPalettesPlugin = Field( + title="Collect Palettes", + default_factory=CollectPalettesPlugin, + ) + + ValidateAudio: ValidateAudioPlugin = Field( + title="Validate Audio", + default_factory=ValidateAudioPlugin, + ) + + ValidateContainers: ValidateContainersPlugin = Field( + title="Validate Containers", + default_factory=ValidateContainersPlugin, + ) + + ValidateSceneSettings: ValidateSceneSettingsPlugin = Field( + title="Validate Scene Settings", + default_factory=ValidateSceneSettingsPlugin, + ) diff --git a/server_addon/harmony/server/version.py b/server_addon/harmony/server/version.py new file mode 100644 index 0000000000..a242f0e757 --- /dev/null +++ b/server_addon/harmony/server/version.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +"""Package declaring addon version.""" +__version__ = "0.1.1" diff --git a/server_addon/hiero/server/__init__.py b/server_addon/hiero/server/__init__.py new file mode 100644 index 0000000000..d0f9bcefc3 --- /dev/null +++ b/server_addon/hiero/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import HieroSettings, DEFAULT_VALUES + + +class HieroAddon(BaseServerAddon): + name = "hiero" + title = "Hiero" + version = __version__ + settings_model: Type[HieroSettings] = HieroSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/hiero/server/settings/__init__.py b/server_addon/hiero/server/settings/__init__.py new file mode 100644 index 0000000000..246c8203e9 --- /dev/null +++ b/server_addon/hiero/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + HieroSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "HieroSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/hiero/server/settings/common.py b/server_addon/hiero/server/settings/common.py new file mode 100644 index 0000000000..eb4791f93e --- /dev/null +++ b/server_addon/hiero/server/settings/common.py @@ -0,0 +1,98 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel +from ayon_server.types import ( + ColorRGBA_float, + ColorRGB_uint8 +) + + +class Vector2d(BaseSettingsModel): + _layout = "compact" + + x: float = Field(1.0, title="X") + y: float = Field(1.0, title="Y") + + +class Vector3d(BaseSettingsModel): + _layout = "compact" + + x: float = Field(1.0, title="X") + y: float = Field(1.0, title="Y") + z: float = Field(1.0, title="Z") + + +def formatable_knob_type_enum(): + return [ + {"value": "text", "label": "Text"}, + {"value": "number", "label": "Number"}, + {"value": "decimal_number", "label": "Decimal number"}, + {"value": "2d_vector", "label": "2D vector"}, + # "3D vector" + ] + + +class Formatable(BaseSettingsModel): + _layout = "compact" + + template: str = Field( + "", + placeholder="""{{key}} or {{key}};{{key}}""", + title="Template" + ) + to_type: str = Field( + "Text", + title="To Knob type", + enum_resolver=formatable_knob_type_enum, + ) + + +knob_types_enum = [ + {"value": "text", "label": "Text"}, + {"value": "formatable", "label": "Formate from template"}, + {"value": "color_gui", "label": "Color GUI"}, + {"value": "boolean", "label": "Boolean"}, + {"value": "number", "label": "Number"}, + {"value": "decimal_number", "label": "Decimal number"}, + {"value": "vector_2d", "label": "2D vector"}, + {"value": "vector_3d", "label": "3D vector"}, + {"value": "color", "label": "Color"} +] + + +class KnobModel(BaseSettingsModel): + _layout = "expanded" + + type: str = Field( + title="Type", + description="Switch between different knob types", + enum_resolver=lambda: knob_types_enum, + conditionalEnum=True + ) + name: str = Field( + title="Name", + placeholder="Name" + ) + text: str = Field("", title="Value") + color_gui: ColorRGB_uint8 = Field( + (0, 0, 255), + title="RGB Uint8", + ) + boolean: bool = Field(False, title="Value") + number: int = Field(0, title="Value") + decimal_number: float = Field(0.0, title="Value") + vector_2d: Vector2d = Field( + default_factory=Vector2d, + title="Value" + ) + vector_3d: Vector3d = Field( + default_factory=Vector3d, + title="Value" + ) + color: ColorRGBA_float = Field( + (0.0, 0.0, 1.0, 1.0), + title="RGBA Float" + ) + formatable: Formatable = Field( + default_factory=Formatable, + title="Value" + ) diff --git a/server_addon/hiero/server/settings/create_plugins.py b/server_addon/hiero/server/settings/create_plugins.py new file mode 100644 index 0000000000..daec4a7cea --- /dev/null +++ b/server_addon/hiero/server/settings/create_plugins.py @@ -0,0 +1,97 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class CreateShotClipModels(BaseSettingsModel): + hierarchy: str = Field( + "{folder}/{sequence}", + title="Shot parent hierarchy", + section="Shot Hierarchy And Rename Settings" + ) + clipRename: bool = Field( + True, + title="Rename clips" + ) + clipName: str = Field( + "{track}{sequence}{shot}", + title="Clip name template" + ) + countFrom: int = Field( + 10, + title="Count sequence from" + ) + countSteps: int = Field( + 10, + title="Stepping number" + ) + + folder: str = Field( + "shots", + title="{folder}", + section="Shot Template Keywords" + ) + episode: str = Field( + "ep01", + title="{episode}" + ) + sequence: str = Field( + "sq01", + title="{sequence}" + ) + track: str = Field( + "{_track_}", + title="{track}" + ) + shot: str = Field( + "sh###", + title="{shot}" + ) + + vSyncOn: bool = Field( + False, + title="Enable Vertical Sync", + section="Vertical Synchronization Of Attributes" + ) + + workfileFrameStart: int = Field( + 1001, + title="Workfiles Start Frame", + section="Shot Attributes" + ) + handleStart: int = Field( + 10, + title="Handle start (head)" + ) + handleEnd: int = Field( + 10, + title="Handle end (tail)" + ) + + +class CreatorPluginsSettings(BaseSettingsModel): + CreateShotClip: CreateShotClipModels = Field( + default_factory=CreateShotClipModels, + title="Create Shot Clip" + ) + + +DEFAULT_CREATE_SETTINGS = { + "create": { + "CreateShotClip": { + "hierarchy": "{folder}/{sequence}", + "clipRename": True, + "clipName": "{track}{sequence}{shot}", + "countFrom": 10, + "countSteps": 10, + "folder": "shots", + "episode": "ep01", + "sequence": "sq01", + "track": "{_track_}", + "shot": "sh###", + "vSyncOn": False, + "workfileFrameStart": 1001, + "handleStart": 10, + "handleEnd": 10 + } + } +} diff --git a/server_addon/hiero/server/settings/filters.py b/server_addon/hiero/server/settings/filters.py new file mode 100644 index 0000000000..7e2702b3b7 --- /dev/null +++ b/server_addon/hiero/server/settings/filters.py @@ -0,0 +1,19 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel, ensure_unique_names + + +class PublishGUIFilterItemModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: bool = Field(True, title="Active") + + +class PublishGUIFiltersModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: list[PublishGUIFilterItemModel] = Field(default_factory=list) + + @validator("value") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value diff --git a/server_addon/hiero/server/settings/imageio.py b/server_addon/hiero/server/settings/imageio.py new file mode 100644 index 0000000000..f2c2728057 --- /dev/null +++ b/server_addon/hiero/server/settings/imageio.py @@ -0,0 +1,169 @@ +from pydantic import Field, validator + +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names, +) + + +def ocio_configs_switcher_enum(): + return [ + {"value": "nuke-default", "label": "nuke-default"}, + {"value": "spi-vfx", "label": "spi-vfx"}, + {"value": "spi-anim", "label": "spi-anim"}, + {"value": "aces_0.1.1", "label": "aces_0.1.1"}, + {"value": "aces_0.7.1", "label": "aces_0.7.1"}, + {"value": "aces_1.0.1", "label": "aces_1.0.1"}, + {"value": "aces_1.0.3", "label": "aces_1.0.3"}, + {"value": "aces_1.1", "label": "aces_1.1"}, + {"value": "aces_1.2", "label": "aces_1.2"}, + {"value": "aces_1.3", "label": "aces_1.3"}, + {"value": "custom", "label": "custom"} + ] + + +class WorkfileColorspaceSettings(BaseSettingsModel): + """Hiero workfile colorspace preset. """ + """# TODO: enhance settings with host api: + we need to add mapping to resolve properly keys. + Hiero is excpecting camel case key names, + but for better code consistency we are using snake_case: + + ocio_config = ocioConfigName + working_space_name = workingSpace + int_16_name = sixteenBitLut + int_8_name = eightBitLut + float_name = floatLut + log_name = logLut + viewer_name = viewerLut + thumbnail_name = thumbnailLut + """ + + ocioConfigName: str = Field( + title="OpenColorIO Config", + description="Switch between OCIO configs", + enum_resolver=ocio_configs_switcher_enum, + conditionalEnum=True + ) + workingSpace: str = Field( + title="Working Space" + ) + viewerLut: str = Field( + title="Viewer" + ) + eightBitLut: str = Field( + title="8-bit files" + ) + sixteenBitLut: str = Field( + title="16-bit files" + ) + logLut: str = Field( + title="Log files" + ) + floatLut: str = Field( + title="Float files" + ) + thumbnailLut: str = Field( + title="Thumnails" + ) + monitorOutLut: str = Field( + title="Monitor" + ) + + +class ClipColorspaceRulesItems(BaseSettingsModel): + _layout = "expanded" + + regex: str = Field("", title="Regex expression") + colorspace: str = Field("", title="Colorspace") + + +class RegexInputsModel(BaseSettingsModel): + inputs: list[ClipColorspaceRulesItems] = Field( + default_factory=list, + title="Inputs" + ) + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ImageIOSettings(BaseSettingsModel): + """Hiero color management project settings. """ + _isGroup: bool = True + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) + workfile: WorkfileColorspaceSettings = Field( + default_factory=WorkfileColorspaceSettings, + title="Workfile" + ) + """# TODO: enhance settings with host api: + - old settings are using `regexInputs` key but we + need to rename to `regex_inputs` + - no need for `inputs` middle part. It can stay + directly on `regex_inputs` + """ + regexInputs: RegexInputsModel = Field( + default_factory=RegexInputsModel, + title="Assign colorspace to clips via rules" + ) + + +DEFAULT_IMAGEIO_SETTINGS = { + "workfile": { + "ocioConfigName": "nuke-default", + "workingSpace": "linear", + "viewerLut": "sRGB", + "eightBitLut": "sRGB", + "sixteenBitLut": "sRGB", + "logLut": "Cineon", + "floatLut": "linear", + "thumbnailLut": "sRGB", + "monitorOutLut": "sRGB" + }, + "regexInputs": { + "inputs": [ + { + "regex": "[^-a-zA-Z0-9](plateRef).*(?=mp4)", + "colorspace": "sRGB" + } + ] + } +} diff --git a/server_addon/hiero/server/settings/loader_plugins.py b/server_addon/hiero/server/settings/loader_plugins.py new file mode 100644 index 0000000000..83b3564c2a --- /dev/null +++ b/server_addon/hiero/server/settings/loader_plugins.py @@ -0,0 +1,38 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class LoadClipModel(BaseSettingsModel): + enabled: bool = Field( + True, + title="Enabled" + ) + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + clip_name_template: str = Field( + title="Clip name template" + ) + + +class LoaderPuginsModel(BaseSettingsModel): + LoadClip: LoadClipModel = Field( + default_factory=LoadClipModel, + title="Load Clip" + ) + + +DEFAULT_LOADER_PLUGINS_SETTINGS = { + "LoadClip": { + "enabled": True, + "product_types": [ + "render2d", + "source", + "plate", + "render", + "review" + ], + "clip_name_template": "{folder[name]}_{product[name]}_{representation}" + } +} diff --git a/server_addon/hiero/server/settings/main.py b/server_addon/hiero/server/settings/main.py new file mode 100644 index 0000000000..47f8110c22 --- /dev/null +++ b/server_addon/hiero/server/settings/main.py @@ -0,0 +1,64 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + +from .imageio import ( + ImageIOSettings, + DEFAULT_IMAGEIO_SETTINGS +) +from .create_plugins import ( + CreatorPluginsSettings, + DEFAULT_CREATE_SETTINGS +) +from .loader_plugins import ( + LoaderPuginsModel, + DEFAULT_LOADER_PLUGINS_SETTINGS +) +from .publish_plugins import ( + PublishPuginsModel, + DEFAULT_PUBLISH_PLUGIN_SETTINGS +) +from .scriptsmenu import ( + ScriptsmenuSettings, + DEFAULT_SCRIPTSMENU_SETTINGS +) +from .filters import PublishGUIFilterItemModel + + +class HieroSettings(BaseSettingsModel): + """Nuke addon settings.""" + + imageio: ImageIOSettings = Field( + default_factory=ImageIOSettings, + title="Color Management (imageio)", + ) + + create: CreatorPluginsSettings = Field( + default_factory=CreatorPluginsSettings, + title="Creator Plugins", + ) + load: LoaderPuginsModel = Field( + default_factory=LoaderPuginsModel, + title="Loader plugins" + ) + publish: PublishPuginsModel = Field( + default_factory=PublishPuginsModel, + title="Publish plugins" + ) + scriptsmenu: ScriptsmenuSettings = Field( + default_factory=ScriptsmenuSettings, + title="Scripts Menu Definition", + ) + filters: list[PublishGUIFilterItemModel] = Field( + default_factory=list + ) + + +DEFAULT_VALUES = { + "imageio": DEFAULT_IMAGEIO_SETTINGS, + "create": DEFAULT_CREATE_SETTINGS, + "load": DEFAULT_LOADER_PLUGINS_SETTINGS, + "publish": DEFAULT_PUBLISH_PLUGIN_SETTINGS, + "scriptsmenu": DEFAULT_SCRIPTSMENU_SETTINGS, + "filters": [], +} diff --git a/server_addon/hiero/server/settings/publish_plugins.py b/server_addon/hiero/server/settings/publish_plugins.py new file mode 100644 index 0000000000..a85e62724b --- /dev/null +++ b/server_addon/hiero/server/settings/publish_plugins.py @@ -0,0 +1,48 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class CollectInstanceVersionModel(BaseSettingsModel): + enabled: bool = Field( + True, + title="Enabled" + ) + + +class ExtractReviewCutUpVideoModel(BaseSettingsModel): + enabled: bool = Field( + True, + title="Enabled" + ) + tags_addition: list[str] = Field( + default_factory=list, + title="Additional tags" + ) + + +class PublishPuginsModel(BaseSettingsModel): + CollectInstanceVersion: CollectInstanceVersionModel = Field( + default_factory=CollectInstanceVersionModel, + title="Collect Instance Version" + ) + """# TODO: enhance settings with host api: + Rename class name and plugin name + to match title (it makes more sense) + """ + ExtractReviewCutUpVideo: ExtractReviewCutUpVideoModel = Field( + default_factory=ExtractReviewCutUpVideoModel, + title="Exctract Review Trim" + ) + + +DEFAULT_PUBLISH_PLUGIN_SETTINGS = { + "CollectInstanceVersion": { + "enabled": False, + }, + "ExtractReviewCutUpVideo": { + "enabled": True, + "tags_addition": [ + "review" + ] + } +} diff --git a/server_addon/hiero/server/settings/scriptsmenu.py b/server_addon/hiero/server/settings/scriptsmenu.py new file mode 100644 index 0000000000..51cb088298 --- /dev/null +++ b/server_addon/hiero/server/settings/scriptsmenu.py @@ -0,0 +1,41 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class ScriptsmenuSubmodel(BaseSettingsModel): + """Item Definition""" + _isGroup = True + + type: str = Field(title="Type") + command: str = Field(title="Command") + sourcetype: str = Field(title="Source Type") + title: str = Field(title="Title") + tooltip: str = Field(title="Tooltip") + + +class ScriptsmenuSettings(BaseSettingsModel): + """Nuke script menu project settings.""" + _isGroup = True + + """# TODO: enhance settings with host api: + - in api rename key `name` to `menu_name` + """ + name: str = Field(title="Menu name") + definition: list[ScriptsmenuSubmodel] = Field( + default_factory=list, + title="Definition", + description="Scriptmenu Items Definition") + + +DEFAULT_SCRIPTSMENU_SETTINGS = { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "sourcetype": "python", + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_hiero')", + "tooltip": "Open the OpenPype Hiero user doc page" + } + ] +} diff --git a/server_addon/hiero/server/version.py b/server_addon/hiero/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/hiero/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/houdini/server/__init__.py b/server_addon/houdini/server/__init__.py new file mode 100644 index 0000000000..870ec2d0b7 --- /dev/null +++ b/server_addon/houdini/server/__init__.py @@ -0,0 +1,17 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import HoudiniSettings, DEFAULT_VALUES + + +class Houdini(BaseServerAddon): + name = "houdini" + title = "Houdini" + version = __version__ + settings_model: Type[HoudiniSettings] = HoudiniSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/houdini/server/settings/__init__.py b/server_addon/houdini/server/settings/__init__.py new file mode 100644 index 0000000000..9fd2678925 --- /dev/null +++ b/server_addon/houdini/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + HoudiniSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "HoudiniSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py new file mode 100644 index 0000000000..88aa40ecd6 --- /dev/null +++ b/server_addon/houdini/server/settings/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class HoudiniImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/houdini/server/settings/main.py b/server_addon/houdini/server/settings/main.py new file mode 100644 index 0000000000..fdb6838f5c --- /dev/null +++ b/server_addon/houdini/server/settings/main.py @@ -0,0 +1,79 @@ +from pydantic import Field +from ayon_server.settings import ( + BaseSettingsModel, + MultiplatformPathModel, + MultiplatformPathListModel, +) + +from .imageio import HoudiniImageIOModel +from .publish_plugins import ( + PublishPluginsModel, + CreatePluginsModel, + DEFAULT_HOUDINI_PUBLISH_SETTINGS, + DEFAULT_HOUDINI_CREATE_SETTINGS +) + + +class ShelfToolsModel(BaseSettingsModel): + name: str = Field(title="Name") + help: str = Field(title="Help text") + script: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Script Path " + ) + icon: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Icon Path " + ) + + +class ShelfDefinitionModel(BaseSettingsModel): + _layout = "expanded" + shelf_name: str = Field(title="Shelf name") + tools_list: list[ShelfToolsModel] = Field( + default_factory=list, + title="Shelf Tools" + ) + + +class ShelvesModel(BaseSettingsModel): + _layout = "expanded" + shelf_set_name: str = Field(title="Shelfs set name") + + shelf_set_source_path: MultiplatformPathListModel = Field( + default_factory=MultiplatformPathListModel, + title="Shelf Set Path (optional)" + ) + + shelf_definition: list[ShelfDefinitionModel] = Field( + default_factory=list, + title="Shelf Definitions" + ) + + +class HoudiniSettings(BaseSettingsModel): + imageio: HoudiniImageIOModel = Field( + default_factory=HoudiniImageIOModel, + title="Color Management (ImageIO)" + ) + shelves: list[ShelvesModel] = Field( + default_factory=list, + title="Houdini Scripts Shelves", + ) + + publish: PublishPluginsModel = Field( + default_factory=PublishPluginsModel, + title="Publish Plugins", + ) + + create: CreatePluginsModel = Field( + default_factory=CreatePluginsModel, + title="Creator Plugins", + ) + + +DEFAULT_VALUES = { + "shelves": [], + "create": DEFAULT_HOUDINI_CREATE_SETTINGS, + "publish": DEFAULT_HOUDINI_PUBLISH_SETTINGS +} diff --git a/server_addon/houdini/server/settings/publish_plugins.py b/server_addon/houdini/server/settings/publish_plugins.py new file mode 100644 index 0000000000..ca5d0a4ea5 --- /dev/null +++ b/server_addon/houdini/server/settings/publish_plugins.py @@ -0,0 +1,150 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +# Creator Plugins +class CreatorModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + defaults: list[str] = Field(title="Default Products") + + +class CreateArnoldAssModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + defaults: list[str] = Field(title="Default Products") + ext: str = Field(Title="Extension") + + +class CreatePluginsModel(BaseSettingsModel): + CreateArnoldAss: CreateArnoldAssModel = Field( + default_factory=CreateArnoldAssModel, + title="Create Alembic Camera") + CreateAlembicCamera: CreatorModel = Field( + default_factory=CreatorModel, + title="Create Alembic Camera") + CreateCompositeSequence: CreatorModel = Field( + default_factory=CreatorModel, + title="Create Composite Sequence") + CreatePointCache: CreatorModel = Field( + default_factory=CreatorModel, + title="Create Point Cache") + CreateRedshiftROP: CreatorModel = Field( + default_factory=CreatorModel, + title="Create RedshiftROP") + CreateRemotePublish: CreatorModel = Field( + default_factory=CreatorModel, + title="Create Remote Publish") + CreateVDBCache: CreatorModel = Field( + default_factory=CreatorModel, + title="Create VDB Cache") + CreateUSD: CreatorModel = Field( + default_factory=CreatorModel, + title="Create USD") + CreateUSDModel: CreatorModel = Field( + default_factory=CreatorModel, + title="Create USD model") + USDCreateShadingWorkspace: CreatorModel = Field( + default_factory=CreatorModel, + title="Create USD shading workspace") + CreateUSDRender: CreatorModel = Field( + default_factory=CreatorModel, + title="Create USD render") + + +DEFAULT_HOUDINI_CREATE_SETTINGS = { + "CreateArnoldAss": { + "enabled": True, + "defaults": [], + "ext": ".ass" + }, + "CreateAlembicCamera": { + "enabled": True, + "defaults": [] + }, + "CreateCompositeSequence": { + "enabled": True, + "defaults": [] + }, + "CreatePointCache": { + "enabled": True, + "defaults": [] + }, + "CreateRedshiftROP": { + "enabled": True, + "defaults": [] + }, + "CreateRemotePublish": { + "enabled": True, + "defaults": [] + }, + "CreateVDBCache": { + "enabled": True, + "defaults": [] + }, + "CreateUSD": { + "enabled": False, + "defaults": [] + }, + "CreateUSDModel": { + "enabled": False, + "defaults": [] + }, + "USDCreateShadingWorkspace": { + "enabled": False, + "defaults": [] + }, + "CreateUSDRender": { + "enabled": False, + "defaults": [] + } +} + + +# Publish Plugins +class ValidateWorkfilePathsModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + node_types: list[str] = Field( + default_factory=list, + title="Node Types" + ) + prohibited_vars: list[str] = Field( + default_factory=list, + title="Prohibited Variables" + ) + + +class ValidateContainersModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + + +class PublishPluginsModel(BaseSettingsModel): + ValidateWorkfilePaths: ValidateWorkfilePathsModel = Field( + default_factory=ValidateWorkfilePathsModel, + title="Validate workfile paths settings.") + ValidateContainers: ValidateContainersModel = Field( + default_factory=ValidateContainersModel, + title="Validate Latest Containers.") + + +DEFAULT_HOUDINI_PUBLISH_SETTINGS = { + "ValidateWorkfilePaths": { + "enabled": True, + "optional": True, + "node_types": [ + "file", + "alembic" + ], + "prohibited_vars": [ + "$HIP", + "$JOB" + ] + }, + "ValidateContainers": { + "enabled": True, + "optional": True, + "active": True + } +} diff --git a/server_addon/houdini/server/version.py b/server_addon/houdini/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/houdini/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/kitsu/server/__init__.py b/server_addon/kitsu/server/__init__.py new file mode 100644 index 0000000000..69cf812dea --- /dev/null +++ b/server_addon/kitsu/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import KitsuSettings, DEFAULT_VALUES + + +class KitsuAddon(BaseServerAddon): + name = "kitsu" + title = "Kitsu" + version = __version__ + settings_model: Type[KitsuSettings] = KitsuSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/kitsu/server/settings.py b/server_addon/kitsu/server/settings.py new file mode 100644 index 0000000000..7afa73ec72 --- /dev/null +++ b/server_addon/kitsu/server/settings.py @@ -0,0 +1,111 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class EntityPattern(BaseSettingsModel): + episode: str = Field(title="Episode") + sequence: str = Field(title="Sequence") + shot: str = Field(title="Shot") + + +def _status_change_cond_enum(): + return [ + {"value": "equal", "label": "Equal"}, + {"value": "not_equal", "label": "Not equal"} + ] + + +class StatusChangeCondition(BaseSettingsModel): + condition: str = Field( + "equal", + enum_resolver=_status_change_cond_enum, + title="Condition" + ) + short_name: str = Field("", title="Short name") + + +class StatusChangeProductTypeRequirementModel(BaseSettingsModel): + condition: str = Field( + "equal", + enum_resolver=_status_change_cond_enum, + title="Condition" + ) + product_type: str = Field("", title="Product type") + + +class StatusChangeConditionsModel(BaseSettingsModel): + status_conditions: list[StatusChangeCondition] = Field( + default_factory=list, + title="Status conditions" + ) + product_type_requirements: list[StatusChangeProductTypeRequirementModel] = Field( + default_factory=list, + title="Product type requirements") + + +class CustomCommentTemplateModel(BaseSettingsModel): + enabled: bool = Field(True) + comment_template: str = Field("", title="Custom comment") + + +class IntegrateKitsuNotes(BaseSettingsModel): + """Kitsu supports markdown and here you can create a custom comment template. + + You can use data from your publishing instance's data. + """ + + set_status_note: bool = Field(title="Set status on note") + note_status_shortname: str = Field(title="Note shortname") + status_change_conditions: StatusChangeConditionsModel = Field( + default_factory=StatusChangeConditionsModel, + title="Status change conditions" + ) + custom_comment_template: CustomCommentTemplateModel = Field( + default_factory=CustomCommentTemplateModel, + title="Custom Comment Template", + ) + + +class PublishPlugins(BaseSettingsModel): + IntegrateKitsuNote: IntegrateKitsuNotes = Field( + default_factory=IntegrateKitsuNotes, + title="Integrate Kitsu Note" + ) + + +class KitsuSettings(BaseSettingsModel): + server: str = Field( + "", + title="Kitsu Server" + ) + entities_naming_pattern: EntityPattern = Field( + default_factory=EntityPattern, + title="Entities naming pattern" + ) + publish: PublishPlugins = Field( + default_factory=PublishPlugins, + title="Publish plugins" + ) + + +DEFAULT_VALUES = { + "entities_naming_pattern": { + "episode": "E##", + "sequence": "SQ##", + "shot": "SH##" + }, + "publish": { + "IntegrateKitsuNote": { + "set_status_note": False, + "note_status_shortname": "wfa", + "status_change_conditions": { + "status_conditions": [], + "product_type_requirements": [] + }, + "custom_comment_template": { + "enabled": False, + "comment_template": "{comment}\n\n| | |\n|--|--|\n| version| `{version}` |\n| product type | `{product[type]}` |\n| name | `{name}` |" + } + } + } +} diff --git a/server_addon/kitsu/server/version.py b/server_addon/kitsu/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/kitsu/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/maya/LICENCE b/server_addon/maya/LICENCE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/server_addon/maya/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/server_addon/maya/README.md b/server_addon/maya/README.md new file mode 100644 index 0000000000..c65c09fba0 --- /dev/null +++ b/server_addon/maya/README.md @@ -0,0 +1,4 @@ +Maya Integration Addon +====================== + +WIP diff --git a/server_addon/maya/server/__init__.py b/server_addon/maya/server/__init__.py new file mode 100644 index 0000000000..8784427dcf --- /dev/null +++ b/server_addon/maya/server/__init__.py @@ -0,0 +1,16 @@ +"""Maya Addon Module""" +from ayon_server.addons import BaseServerAddon + +from .settings.main import MayaSettings, DEFAULT_MAYA_SETTING +from .version import __version__ + + +class MayaAddon(BaseServerAddon): + name = "maya" + title = "Maya" + version = __version__ + settings_model = MayaSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_MAYA_SETTING) diff --git a/server_addon/maya/server/settings/__init__.py b/server_addon/maya/server/settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/server_addon/maya/server/settings/creators.py b/server_addon/maya/server/settings/creators.py new file mode 100644 index 0000000000..3756d45e6c --- /dev/null +++ b/server_addon/maya/server/settings/creators.py @@ -0,0 +1,408 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class CreateLookModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + make_tx: bool = Field(title="Make tx files") + rs_tex: bool = Field(title="Make Redshift texture files") + defaults: list[str] = Field( + default_factory=["Main"], title="Default Products" + ) + + +class BasicCreatorModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + defaults: list[str] = Field( + default_factory=list, + title="Default Products" + ) + + +class CreateUnrealStaticMeshModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + defaults: list[str] = Field( + default_factory=["", "_Main"], + title="Default Products" + ) + static_mesh_prefixes: str = Field("S", title="Static Mesh Prefix") + collision_prefixes: list[str] = Field( + default_factory=["UBX", "UCP", "USP", "UCX"], + title="Collision Prefixes" + ) + + +class CreateUnrealSkeletalMeshModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + defaults: list[str] = Field(default_factory=[], title="Default Products") + joint_hints: str = Field("jnt_org", title="Joint root hint") + + +class CreateMultiverseLookModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + publish_mip_map: bool = Field(title="publish_mip_map") + + +class BasicExportMeshModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + write_color_sets: bool = Field(title="Write Color Sets") + write_face_sets: bool = Field(title="Write Face Sets") + defaults: list[str] = Field( + default_factory=list, + title="Default Products" + ) + + +class CreateAnimationModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + write_color_sets: bool = Field(title="Write Color Sets") + write_face_sets: bool = Field(title="Write Face Sets") + include_parent_hierarchy: bool = Field( + title="Include Parent Hierarchy") + include_user_defined_attributes: bool = Field( + title="Include User Defined Attributes") + defaults: list[str] = Field( + default_factory=list, + title="Default Products" + ) + + +class CreatePointCacheModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + write_color_sets: bool = Field(title="Write Color Sets") + write_face_sets: bool = Field(title="Write Face Sets") + include_user_defined_attributes: bool = Field( + title="Include User Defined Attributes" + ) + defaults: list[str] = Field( + default_factory=["Main"], + title="Default Products" + ) + + +class CreateProxyAlembicModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + write_color_sets: bool = Field(title="Write Color Sets") + write_face_sets: bool = Field(title="Write Face Sets") + defaults: list[str] = Field( + default_factory=["Main"], + title="Default Products" + ) + + +class CreateAssModel(BasicCreatorModel): + expandProcedurals: bool = Field(title="Expand Procedurals") + motionBlur: bool = Field(title="Motion Blur") + motionBlurKeys: int = Field(2, title="Motion Blur Keys") + motionBlurLength: float = Field(0.5, title="Motion Blur Length") + maskOptions: bool = Field(title="Mask Options") + maskCamera: bool = Field(title="Mask Camera") + maskLight: bool = Field(title="Mask Light") + maskShape: bool = Field(title="Mask Shape") + maskShader: bool = Field(title="Mask Shader") + maskOverride: bool = Field(title="Mask Override") + maskDriver: bool = Field(title="Mask Driver") + maskFilter: bool = Field(title="Mask Filter") + maskColor_manager: bool = Field(title="Mask Color Manager") + maskOperator: bool = Field(title="Mask Operator") + + +class CreateReviewModel(BasicCreatorModel): + useMayaTimeline: bool = Field(title="Use Maya Timeline for Frame Range.") + + +class CreateVrayProxyModel(BaseSettingsModel): + enabled: bool = Field(True) + vrmesh: bool = Field(title="VrMesh") + alembic: bool = Field(title="Alembic") + defaults: list[str] = Field(default_factory=list, title="Default Products") + + +class CreatorsModel(BaseSettingsModel): + CreateLook: CreateLookModel = Field( + default_factory=CreateLookModel, + title="Create Look" + ) + CreateRender: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Render" + ) + # "-" is not compatible in the new model + CreateUnrealStaticMesh: CreateUnrealStaticMeshModel = Field( + default_factory=CreateUnrealStaticMeshModel, + title="Create Unreal_Static Mesh" + ) + # "-" is not compatible in the new model + CreateUnrealSkeletalMesh: CreateUnrealSkeletalMeshModel = Field( + default_factory=CreateUnrealSkeletalMeshModel, + title="Create Unreal_Skeletal Mesh" + ) + CreateMultiverseLook: CreateMultiverseLookModel = Field( + default_factory=CreateMultiverseLookModel, + title="Create Multiverse Look" + ) + CreateAnimation: CreateAnimationModel = Field( + default_factory=CreateAnimationModel, + title="Create Animation" + ) + CreateModel: BasicExportMeshModel = Field( + default_factory=BasicExportMeshModel, + title="Create Model" + ) + CreatePointCache: CreatePointCacheModel = Field( + default_factory=CreatePointCacheModel, + title="Create Point Cache" + ) + CreateProxyAlembic: CreateProxyAlembicModel = Field( + default_factory=CreateProxyAlembicModel, + title="Create Proxy Alembic" + ) + CreateMultiverseUsd: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Multiverse USD" + ) + CreateMultiverseUsdComp: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Multiverse USD Composition" + ) + CreateMultiverseUsdOver: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Multiverse USD Override" + ) + CreateAss: CreateAssModel = Field( + default_factory=CreateAssModel, + title="Create Ass" + ) + CreateAssembly: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Assembly" + ) + CreateCamera: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Camera" + ) + CreateLayout: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Layout" + ) + CreateMayaScene: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Maya Scene" + ) + CreateRenderSetup: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Render Setup" + ) + CreateReview: CreateReviewModel = Field( + default_factory=CreateReviewModel, + title="Create Review" + ) + CreateRig: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Rig" + ) + CreateSetDress: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Set Dress" + ) + CreateVrayProxy: CreateVrayProxyModel = Field( + default_factory=CreateVrayProxyModel, + title="Create VRay Proxy" + ) + CreateVRayScene: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create VRay Scene" + ) + CreateYetiRig: BasicCreatorModel = Field( + default_factory=BasicCreatorModel, + title="Create Yeti Rig" + ) + + +DEFAULT_CREATORS_SETTINGS = { + "CreateLook": { + "enabled": True, + "make_tx": True, + "rs_tex": False, + "defaults": [ + "Main" + ] + }, + "CreateRender": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateUnrealStaticMesh": { + "enabled": True, + "defaults": [ + "", + "_Main" + ], + "static_mesh_prefix": "S", + "collision_prefixes": [ + "UBX", + "UCP", + "USP", + "UCX" + ] + }, + "CreateUnrealSkeletalMesh": { + "enabled": True, + "defaults": [], + "joint_hints": "jnt_org" + }, + "CreateMultiverseLook": { + "enabled": True, + "publish_mip_map": True + }, + "CreateAnimation": { + "enabled": False, + "write_color_sets": False, + "write_face_sets": False, + "include_parent_hierarchy": False, + "include_user_defined_attributes": False, + "defaults": [ + "Main" + ] + }, + "CreateModel": { + "enabled": True, + "write_color_sets": False, + "write_face_sets": False, + "defaults": [ + "Main", + "Proxy", + "Sculpt" + ] + }, + "CreatePointCache": { + "enabled": True, + "write_color_sets": False, + "write_face_sets": False, + "include_user_defined_attributes": False, + "defaults": [ + "Main" + ] + }, + "CreateProxyAlembic": { + "enabled": True, + "write_color_sets": False, + "write_face_sets": False, + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsd": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsdComp": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateMultiverseUsdOver": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateAss": { + "enabled": True, + "defaults": [ + "Main" + ], + "expandProcedurals": False, + "motionBlur": True, + "motionBlurKeys": 2, + "motionBlurLength": 0.5, + "maskOptions": False, + "maskCamera": False, + "maskLight": False, + "maskShape": False, + "maskShader": False, + "maskOverride": False, + "maskDriver": False, + "maskFilter": False, + "maskColor_manager": False, + "maskOperator": False + }, + "CreateAssembly": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateCamera": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateLayout": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateMayaScene": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateRenderSetup": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateReview": { + "enabled": True, + "defaults": [ + "Main" + ], + "useMayaTimeline": True + }, + "CreateRig": { + "enabled": True, + "defaults": [ + "Main", + "Sim", + "Cloth" + ] + }, + "CreateSetDress": { + "enabled": True, + "defaults": [ + "Main", + "Anim" + ] + }, + "CreateVrayProxy": { + "enabled": True, + "vrmesh": True, + "alembic": True, + "defaults": [ + "Main" + ] + }, + "CreateVRayScene": { + "enabled": True, + "defaults": [ + "Main" + ] + }, + "CreateYetiRig": { + "enabled": True, + "defaults": [ + "Main" + ] + } +} diff --git a/server_addon/maya/server/settings/explicit_plugins_loading.py b/server_addon/maya/server/settings/explicit_plugins_loading.py new file mode 100644 index 0000000000..394adb728f --- /dev/null +++ b/server_addon/maya/server/settings/explicit_plugins_loading.py @@ -0,0 +1,429 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class PluginsModel(BaseSettingsModel): + _layout = "expanded" + enabled: bool = Field(title="Enabled") + name: str = Field("", title="Name") + + +class ExplicitPluginsLoadingModel(BaseSettingsModel): + """Maya Explicit Plugins Loading.""" + _isGroup: bool = True + enabled: bool = Field(title="enabled") + plugins_to_load: list[PluginsModel] = Field( + default_factory=list, title="Plugins To Load" + ) + + +DEFAULT_EXPLITCIT_PLUGINS_LOADING_SETTINGS = { + "enabled": False, + "plugins_to_load": [ + { + "enabled": False, + "name": "AbcBullet" + }, + { + "enabled": True, + "name": "AbcExport" + }, + { + "enabled": True, + "name": "AbcImport" + }, + { + "enabled": False, + "name": "animImportExport" + }, + { + "enabled": False, + "name": "ArubaTessellator" + }, + { + "enabled": False, + "name": "ATFPlugin" + }, + { + "enabled": False, + "name": "atomImportExport" + }, + { + "enabled": False, + "name": "AutodeskPacketFile" + }, + { + "enabled": False, + "name": "autoLoader" + }, + { + "enabled": False, + "name": "bifmeshio" + }, + { + "enabled": False, + "name": "bifrostGraph" + }, + { + "enabled": False, + "name": "bifrostshellnode" + }, + { + "enabled": False, + "name": "bifrostvisplugin" + }, + { + "enabled": False, + "name": "blast2Cmd" + }, + { + "enabled": False, + "name": "bluePencil" + }, + { + "enabled": False, + "name": "Boss" + }, + { + "enabled": False, + "name": "bullet" + }, + { + "enabled": True, + "name": "cacheEvaluator" + }, + { + "enabled": False, + "name": "cgfxShader" + }, + { + "enabled": False, + "name": "cleanPerFaceAssignment" + }, + { + "enabled": False, + "name": "clearcoat" + }, + { + "enabled": False, + "name": "convertToComponentTags" + }, + { + "enabled": False, + "name": "curveWarp" + }, + { + "enabled": False, + "name": "ddsFloatReader" + }, + { + "enabled": True, + "name": "deformerEvaluator" + }, + { + "enabled": False, + "name": "dgProfiler" + }, + { + "enabled": False, + "name": "drawUfe" + }, + { + "enabled": False, + "name": "dx11Shader" + }, + { + "enabled": False, + "name": "fbxmaya" + }, + { + "enabled": False, + "name": "fltTranslator" + }, + { + "enabled": False, + "name": "freeze" + }, + { + "enabled": False, + "name": "Fur" + }, + { + "enabled": False, + "name": "gameFbxExporter" + }, + { + "enabled": False, + "name": "gameInputDevice" + }, + { + "enabled": False, + "name": "GamePipeline" + }, + { + "enabled": False, + "name": "gameVertexCount" + }, + { + "enabled": False, + "name": "geometryReport" + }, + { + "enabled": False, + "name": "geometryTools" + }, + { + "enabled": False, + "name": "glslShader" + }, + { + "enabled": True, + "name": "GPUBuiltInDeformer" + }, + { + "enabled": False, + "name": "gpuCache" + }, + { + "enabled": False, + "name": "hairPhysicalShader" + }, + { + "enabled": False, + "name": "ik2Bsolver" + }, + { + "enabled": False, + "name": "ikSpringSolver" + }, + { + "enabled": False, + "name": "invertShape" + }, + { + "enabled": False, + "name": "lges" + }, + { + "enabled": False, + "name": "lookdevKit" + }, + { + "enabled": False, + "name": "MASH" + }, + { + "enabled": False, + "name": "matrixNodes" + }, + { + "enabled": False, + "name": "mayaCharacterization" + }, + { + "enabled": False, + "name": "mayaHIK" + }, + { + "enabled": False, + "name": "MayaMuscle" + }, + { + "enabled": False, + "name": "mayaUsdPlugin" + }, + { + "enabled": False, + "name": "mayaVnnPlugin" + }, + { + "enabled": False, + "name": "melProfiler" + }, + { + "enabled": False, + "name": "meshReorder" + }, + { + "enabled": True, + "name": "modelingToolkit" + }, + { + "enabled": False, + "name": "mtoa" + }, + { + "enabled": False, + "name": "mtoh" + }, + { + "enabled": False, + "name": "nearestPointOnMesh" + }, + { + "enabled": True, + "name": "objExport" + }, + { + "enabled": False, + "name": "OneClick" + }, + { + "enabled": False, + "name": "OpenEXRLoader" + }, + { + "enabled": False, + "name": "pgYetiMaya" + }, + { + "enabled": False, + "name": "pgyetiVrayMaya" + }, + { + "enabled": False, + "name": "polyBoolean" + }, + { + "enabled": False, + "name": "poseInterpolator" + }, + { + "enabled": False, + "name": "quatNodes" + }, + { + "enabled": False, + "name": "randomizerDevice" + }, + { + "enabled": False, + "name": "redshift4maya" + }, + { + "enabled": True, + "name": "renderSetup" + }, + { + "enabled": False, + "name": "retargeterNodes" + }, + { + "enabled": False, + "name": "RokokoMotionLibrary" + }, + { + "enabled": False, + "name": "rotateHelper" + }, + { + "enabled": False, + "name": "sceneAssembly" + }, + { + "enabled": False, + "name": "shaderFXPlugin" + }, + { + "enabled": False, + "name": "shotCamera" + }, + { + "enabled": False, + "name": "snapTransform" + }, + { + "enabled": False, + "name": "stage" + }, + { + "enabled": True, + "name": "stereoCamera" + }, + { + "enabled": False, + "name": "stlTranslator" + }, + { + "enabled": False, + "name": "studioImport" + }, + { + "enabled": False, + "name": "Substance" + }, + { + "enabled": False, + "name": "substancelink" + }, + { + "enabled": False, + "name": "substancemaya" + }, + { + "enabled": False, + "name": "substanceworkflow" + }, + { + "enabled": False, + "name": "svgFileTranslator" + }, + { + "enabled": False, + "name": "sweep" + }, + { + "enabled": False, + "name": "testify" + }, + { + "enabled": False, + "name": "tiffFloatReader" + }, + { + "enabled": False, + "name": "timeSliderBookmark" + }, + { + "enabled": False, + "name": "Turtle" + }, + { + "enabled": False, + "name": "Type" + }, + { + "enabled": False, + "name": "udpDevice" + }, + { + "enabled": False, + "name": "ufeSupport" + }, + { + "enabled": False, + "name": "Unfold3D" + }, + { + "enabled": False, + "name": "VectorRender" + }, + { + "enabled": False, + "name": "vrayformaya" + }, + { + "enabled": False, + "name": "vrayvolumegrid" + }, + { + "enabled": False, + "name": "xgenToolkit" + }, + { + "enabled": False, + "name": "xgenVray" + } + ] +} diff --git a/server_addon/maya/server/settings/imageio.py b/server_addon/maya/server/settings/imageio.py new file mode 100644 index 0000000000..7512bfe253 --- /dev/null +++ b/server_addon/maya/server/settings/imageio.py @@ -0,0 +1,126 @@ +"""Providing models and setting values for image IO in Maya. + +Note: Names were changed to get rid of the versions in class names. +""" +from pydantic import Field, validator + +from ayon_server.settings import BaseSettingsModel, ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ColorManagementPreferenceV2Model(BaseSettingsModel): + """Color Management Preference v2 (Maya 2022+).""" + _layout = "expanded" + + enabled: bool = Field(True, title="Use Color Management Preference v2") + + renderSpace: str = Field(title="Rendering Space") + displayName: str = Field(title="Display") + viewName: str = Field(title="View") + + +class ColorManagementPreferenceModel(BaseSettingsModel): + """Color Management Preference (legacy).""" + _layout = "expanded" + + renderSpace: str = Field(title="Rendering Space") + viewTransform: str = Field(title="Viewer Transform ") + + +class WorkfileImageIOModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + renderSpace: str = Field(title="Rendering Space") + displayName: str = Field(title="Display") + viewName: str = Field(title="View") + + +class ImageIOSettings(BaseSettingsModel): + """Maya color management project settings. + + Todo: What to do with color management preferences version? + """ + + _isGroup: bool = True + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) + workfile: WorkfileImageIOModel = Field( + default_factory=WorkfileImageIOModel, + title="Workfile" + ) + # Deprecated + colorManagementPreference_v2: ColorManagementPreferenceV2Model = Field( + default_factory=ColorManagementPreferenceV2Model, + title="Color Management Preference v2 (Maya 2022+)" + ) + colorManagementPreference: ColorManagementPreferenceModel = Field( + default_factory=ColorManagementPreferenceModel, + title="Color Management Preference (legacy)" + ) + + +DEFAULT_IMAGEIO_SETTINGS = { + "activate_host_color_management": True, + "ocio_config": { + "override_global_config": False, + "filepath": [] + }, + "file_rules": { + "activate_host_rules": False, + "rules": [] + }, + "workfile": { + "enabled": False, + "renderSpace": "ACES - ACEScg", + "displayName": "ACES", + "viewName": "sRGB" + }, + "colorManagementPreference_v2": { + "enabled": True, + "renderSpace": "ACEScg", + "displayName": "sRGB", + "viewName": "ACES 1.0 SDR-video" + }, + "colorManagementPreference": { + "renderSpace": "scene-linear Rec 709/sRGB", + "viewTransform": "sRGB gamma" + } +} diff --git a/server_addon/maya/server/settings/include_handles.py b/server_addon/maya/server/settings/include_handles.py new file mode 100644 index 0000000000..3ba6aca66b --- /dev/null +++ b/server_addon/maya/server/settings/include_handles.py @@ -0,0 +1,30 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel, task_types_enum + + +class IncludeByTaskTypeModel(BaseSettingsModel): + task_type: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + include_handles: bool = Field(True, title="Include handles") + + +class IncludeHandlesModel(BaseSettingsModel): + """Maya dirmap settings.""" + # _layout = "expanded" + include_handles_default: bool = Field( + True, title="Include handles by default" + ) + per_task_type: list[IncludeByTaskTypeModel] = Field( + default_factory=list, + title="Include/exclude handles by task type" + ) + + +DEFAULT_INCLUDE_HANDLES = { + "include_handles_default": False, + "per_task_type": [] +} diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py new file mode 100644 index 0000000000..60fc2a1cdd --- /dev/null +++ b/server_addon/maya/server/settings/loaders.py @@ -0,0 +1,115 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel +from ayon_server.types import ColorRGBA_uint8 + + +class ColorsSetting(BaseSettingsModel): + model: ColorRGBA_uint8 = Field( + (209, 132, 30, 1.0), title="Model:") + rig: ColorRGBA_uint8 = Field( + (59, 226, 235, 1.0), title="Rig:") + pointcache: ColorRGBA_uint8 = Field( + (94, 209, 30, 1.0), title="Pointcache:") + animation: ColorRGBA_uint8 = Field( + (94, 209, 30, 1.0), title="Animation:") + ass: ColorRGBA_uint8 = Field( + (249, 135, 53, 1.0), title="Arnold StandIn:") + camera: ColorRGBA_uint8 = Field( + (136, 114, 244, 1.0), title="Camera:") + fbx: ColorRGBA_uint8 = Field( + (215, 166, 255, 1.0), title="FBX:") + mayaAscii: ColorRGBA_uint8 = Field( + (67, 174, 255, 1.0), title="Maya Ascii:") + mayaScene: ColorRGBA_uint8 = Field( + (67, 174, 255, 1.0), title="Maya Scene:") + setdress: ColorRGBA_uint8 = Field( + (255, 250, 90, 1.0), title="Set Dress:") + layout: ColorRGBA_uint8 = Field(( + 255, 250, 90, 1.0), title="Layout:") + vdbcache: ColorRGBA_uint8 = Field( + (249, 54, 0, 1.0), title="VDB Cache:") + vrayproxy: ColorRGBA_uint8 = Field( + (255, 150, 12, 1.0), title="VRay Proxy:") + vrayscene_layer: ColorRGBA_uint8 = Field( + (255, 150, 12, 1.0), title="VRay Scene:") + yeticache: ColorRGBA_uint8 = Field( + (99, 206, 220, 1.0), title="Yeti Cache:") + yetiRig: ColorRGBA_uint8 = Field( + (0, 205, 125, 1.0), title="Yeti Rig:") + + +class ReferenceLoaderModel(BaseSettingsModel): + namespace: str = Field(title="Namespace") + group_name: str = Field(title="Group name") + display_handle: bool = Field(title="Display Handle On Load References") + + +class LoadersModel(BaseSettingsModel): + colors: ColorsSetting = Field( + default_factory=ColorsSetting, + title="Loaded Products Outliner Colors") + + reference_loader: ReferenceLoaderModel = Field( + default_factory=ReferenceLoaderModel, + title="Reference Loader" + ) + + +DEFAULT_LOADERS_SETTING = { + "colors": { + "model": [ + 209, 132, 30, 1.0 + ], + "rig": [ + 59, 226, 235, 1.0 + ], + "pointcache": [ + 94, 209, 30, 1.0 + ], + "animation": [ + 94, 209, 30, 1.0 + ], + "ass": [ + 249, 135, 53, 1.0 + ], + "camera": [ + 136, 114, 244, 1.0 + ], + "fbx": [ + 215, 166, 255, 1.0 + ], + "mayaAscii": [ + 67, 174, 255, 1.0 + ], + "mayaScene": [ + 67, 174, 255, 1.0 + ], + "setdress": [ + 255, 250, 90, 1.0 + ], + "layout": [ + 255, 250, 90, 1.0 + ], + "vdbcache": [ + 249, 54, 0, 1.0 + ], + "vrayproxy": [ + 255, 150, 12, 1.0 + ], + "vrayscene_layer": [ + 255, 150, 12, 1.0 + ], + "yeticache": [ + 99, 206, 220, 1.0 + ], + "yetiRig": [ + 0, 205, 125, 1.0 + ] + }, + "reference_loader": { + "namespace": "{folder[name]}_{product[name]}_##_", + "group_name": "_GRP", + "display_handle": True + } +} diff --git a/server_addon/maya/server/settings/main.py b/server_addon/maya/server/settings/main.py new file mode 100644 index 0000000000..47f4121584 --- /dev/null +++ b/server_addon/maya/server/settings/main.py @@ -0,0 +1,139 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel, ensure_unique_names +from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS +from .maya_dirmap import MayaDirmapModel, DEFAULT_MAYA_DIRMAP_SETTINGS +from .include_handles import IncludeHandlesModel, DEFAULT_INCLUDE_HANDLES +from .explicit_plugins_loading import ( + ExplicitPluginsLoadingModel, DEFAULT_EXPLITCIT_PLUGINS_LOADING_SETTINGS +) +from .scriptsmenu import ScriptsmenuModel, DEFAULT_SCRIPTSMENU_SETTINGS +from .render_settings import RenderSettingsModel, DEFAULT_RENDER_SETTINGS +from .creators import CreatorsModel, DEFAULT_CREATORS_SETTINGS +from .publishers import PublishersModel, DEFAULT_PUBLISH_SETTINGS +from .loaders import LoadersModel, DEFAULT_LOADERS_SETTING +from .workfile_build_settings import ProfilesModel, DEFAULT_WORKFILE_SETTING +from .templated_workfile_settings import ( + TemplatedProfilesModel, DEFAULT_TEMPLATED_WORKFILE_SETTINGS +) + + +class ExtMappingItemModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Product type") + value: str = Field(title="Extension") + + +class PublishGUIFilterItemModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: bool = Field(True, title="Active") + + +class PublishGUIFiltersModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: list[PublishGUIFilterItemModel] = Field(default_factory=list) + + @validator("value") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class MayaSettings(BaseSettingsModel): + """Maya Project Settings.""" + + open_workfile_post_initialization: bool = Field( + True, title="Open Workfile Post Initialization") + explicit_plugins_loading: ExplicitPluginsLoadingModel = Field( + default_factory=ExplicitPluginsLoadingModel, + title="Explicit Plugins Loading") + imageio: ImageIOSettings = Field( + default_factory=ImageIOSettings, title="Color Management (imageio)") + mel_workspace: str = Field(title="Maya MEL Workspace", widget="textarea") + ext_mapping: list[ExtMappingItemModel] = Field( + default_factory=list, title="Extension Mapping") + maya_dirmap: MayaDirmapModel = Field( + default_factory=MayaDirmapModel, title="Maya dirmap Settings") + include_handles: IncludeHandlesModel = Field( + default_factory=IncludeHandlesModel, + title="Include/Exclude Handles in default playback & render range" + ) + scriptsmenu: ScriptsmenuModel = Field( + default_factory=ScriptsmenuModel, title="Scriptsmenu Settings") + render_settings: RenderSettingsModel = Field( + default_factory=RenderSettingsModel, title="Render Settings") + create: CreatorsModel = Field( + default_factory=CreatorsModel, title="Creators") + publish: PublishersModel = Field( + default_factory=PublishersModel, title="Publishers") + load: LoadersModel = Field( + default_factory=LoadersModel, title="Loaders") + workfile_build: ProfilesModel = Field( + default_factory=ProfilesModel, title="Workfile Build Settings") + templated_workfile_build: TemplatedProfilesModel = Field( + default_factory=TemplatedProfilesModel, + title="Templated Workfile Build Settings") + filters: list[PublishGUIFiltersModel] = Field( + default_factory=list, + title="Publish GUI Filters") + + @validator("filters", "ext_mapping") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +DEFAULT_MEL_WORKSPACE_SETTINGS = "\n".join(( + 'workspace -fr "shaders" "renderData/shaders";', + 'workspace -fr "images" "renders/maya";', + 'workspace -fr "particles" "particles";', + 'workspace -fr "mayaAscii" "";', + 'workspace -fr "mayaBinary" "";', + 'workspace -fr "scene" "";', + 'workspace -fr "alembicCache" "cache/alembic";', + 'workspace -fr "renderData" "renderData";', + 'workspace -fr "sourceImages" "sourceimages";', + 'workspace -fr "fileCache" "cache/nCache";', + '', +)) + +DEFAULT_MAYA_SETTING = { + "open_workfile_post_initialization": False, + "explicit_plugins_loading": DEFAULT_EXPLITCIT_PLUGINS_LOADING_SETTINGS, + "imageio": DEFAULT_IMAGEIO_SETTINGS, + "mel_workspace": DEFAULT_MEL_WORKSPACE_SETTINGS, + "ext_mapping": [ + {"name": "model", "value": "ma"}, + {"name": "mayaAscii", "value": "ma"}, + {"name": "camera", "value": "ma"}, + {"name": "rig", "value": "ma"}, + {"name": "workfile", "value": "ma"}, + {"name": "yetiRig", "value": "ma"} + ], + # `maya_dirmap` was originally with dash - `maya-dirmap` + "maya_dirmap": DEFAULT_MAYA_DIRMAP_SETTINGS, + "include_handles": DEFAULT_INCLUDE_HANDLES, + "scriptsmenu": DEFAULT_SCRIPTSMENU_SETTINGS, + "render_settings": DEFAULT_RENDER_SETTINGS, + "create": DEFAULT_CREATORS_SETTINGS, + "publish": DEFAULT_PUBLISH_SETTINGS, + "load": DEFAULT_LOADERS_SETTING, + "workfile_build": DEFAULT_WORKFILE_SETTING, + "templated_workfile_build": DEFAULT_TEMPLATED_WORKFILE_SETTINGS, + "filters": [ + { + "name": "preset 1", + "value": [ + {"name": "ValidateNoAnimation", "value": False}, + {"name": "ValidateShapeDefaultNames", "value": False}, + ] + }, + { + "name": "preset 2", + "value": [ + {"name": "ValidateNoAnimation", "value": False}, + ] + }, + ] +} diff --git a/server_addon/maya/server/settings/maya_dirmap.py b/server_addon/maya/server/settings/maya_dirmap.py new file mode 100644 index 0000000000..243261dc87 --- /dev/null +++ b/server_addon/maya/server/settings/maya_dirmap.py @@ -0,0 +1,40 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class MayaDirmapPathsSubmodel(BaseSettingsModel): + _layout = "compact" + source_path: list[str] = Field( + default_factory=list, title="Source Paths" + ) + destination_path: list[str] = Field( + default_factory=list, title="Destination Paths" + ) + + +class MayaDirmapModel(BaseSettingsModel): + """Maya dirmap settings.""" + # _layout = "expanded" + _isGroup: bool = True + + enabled: bool = Field(title="enabled") + # Use ${} placeholder instead of absolute value of a root in + # referenced filepaths. + use_env_var_as_root: bool = Field( + title="Use env var placeholder in referenced paths" + ) + paths: MayaDirmapPathsSubmodel = Field( + default_factory=MayaDirmapPathsSubmodel, + title="Dirmap Paths" + ) + + +DEFAULT_MAYA_DIRMAP_SETTINGS = { + "use_env_var_as_root": False, + "enabled": False, + "paths": { + "source-path": [], + "destination-path": [] + } +} diff --git a/server_addon/maya/server/settings/publish_playblast.py b/server_addon/maya/server/settings/publish_playblast.py new file mode 100644 index 0000000000..acfcaf5988 --- /dev/null +++ b/server_addon/maya/server/settings/publish_playblast.py @@ -0,0 +1,382 @@ +from pydantic import Field, validator + +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names, + task_types_enum, +) +from ayon_server.types import ColorRGBA_uint8 + + +def hardware_falloff_enum(): + return [ + {"label": "Linear", "value": "0"}, + {"label": "Exponential", "value": "1"}, + {"label": "Exponential Squared", "value": "2"} + ] + + +def renderer_enum(): + return [ + {"label": "Viewport 2.0", "value": "vp2Renderer"} + ] + + +def displayLights_enum(): + return [ + {"label": "Default Lighting", "value": "default"}, + {"label": "All Lights", "value": "all"}, + {"label": "Selected Lights", "value": "selected"}, + {"label": "Flat Lighting", "value": "flat"}, + {"label": "No Lights", "value": "nolights"} + ] + + +def plugin_objects_default(): + return [ + { + "name": "gpuCacheDisplayFilter", + "value": False + } + ] + + +class CodecSetting(BaseSettingsModel): + _layout = "expanded" + compression: str = Field("png", title="Encoding") + format: str = Field("image", title="Format") + quality: int = Field(95, title="Quality", ge=0, le=100) + + +class DisplayOptionsSetting(BaseSettingsModel): + _layout = "expanded" + override_display: bool = Field(True, title="Override display options") + background: ColorRGBA_uint8 = Field( + (125, 125, 125, 1.0), title="Background Color" + ) + displayGradient: bool = Field(True, title="Display background gradient") + backgroundTop: ColorRGBA_uint8 = Field( + (125, 125, 125, 1.0), title="Background Top" + ) + backgroundBottom: ColorRGBA_uint8 = Field( + (125, 125, 125, 1.0), title="Background Bottom" + ) + + +class GenericSetting(BaseSettingsModel): + _layout = "expanded" + isolate_view: bool = Field(True, title="Isolate View") + off_screen: bool = Field(True, title="Off Screen") + pan_zoom: bool = Field(False, title="2D Pan/Zoom") + + +class RendererSetting(BaseSettingsModel): + _layout = "expanded" + rendererName: str = Field( + "vp2Renderer", + enum_resolver=renderer_enum, + title="Renderer name" + ) + + +class ResolutionSetting(BaseSettingsModel): + _layout = "expanded" + width: int = Field(0, title="Width") + height: int = Field(0, title="Height") + + +class PluginObjectsModel(BaseSettingsModel): + name: str = Field("", title="Name") + value: bool = Field(True, title="Enabled") + + +class ViewportOptionsSetting(BaseSettingsModel): + override_viewport_options: bool = Field( + True, title="Override viewport options" + ) + displayLights: str = Field( + "default", enum_resolver=displayLights_enum, title="Display Lights" + ) + displayTextures: bool = Field(True, title="Display Textures") + textureMaxResolution: int = Field(1024, title="Texture Clamp Resolution") + renderDepthOfField: bool = Field( + True, title="Depth of Field", section="Depth of Field" + ) + shadows: bool = Field(True, title="Display Shadows") + twoSidedLighting: bool = Field(True, title="Two Sided Lighting") + lineAAEnable: bool = Field( + True, title="Enable Anti-Aliasing", section="Anti-Aliasing" + ) + multiSample: int = Field(8, title="Anti Aliasing Samples") + useDefaultMaterial: bool = Field(False, title="Use Default Material") + wireframeOnShaded: bool = Field(False, title="Wireframe On Shaded") + xray: bool = Field(False, title="X-Ray") + jointXray: bool = Field(False, title="X-Ray Joints") + backfaceCulling: bool = Field(False, title="Backface Culling") + ssaoEnable: bool = Field( + False, title="Screen Space Ambient Occlusion", section="SSAO" + ) + ssaoAmount: int = Field(1, title="SSAO Amount") + ssaoRadius: int = Field(16, title="SSAO Radius") + ssaoFilterRadius: int = Field(16, title="SSAO Filter Radius") + ssaoSamples: int = Field(16, title="SSAO Samples") + fogging: bool = Field(False, title="Enable Hardware Fog", section="Fog") + hwFogFalloff: str = Field( + "0", enum_resolver=hardware_falloff_enum, title="Hardware Falloff" + ) + hwFogDensity: float = Field(0.0, title="Fog Density") + hwFogStart: int = Field(0, title="Fog Start") + hwFogEnd: int = Field(100, title="Fog End") + hwFogAlpha: int = Field(0, title="Fog Alpha") + hwFogColorR: float = Field(1.0, title="Fog Color R") + hwFogColorG: float = Field(1.0, title="Fog Color G") + hwFogColorB: float = Field(1.0, title="Fog Color B") + motionBlurEnable: bool = Field( + False, title="Enable Motion Blur", section="Motion Blur" + ) + motionBlurSampleCount: int = Field(8, title="Motion Blur Sample Count") + motionBlurShutterOpenFraction: float = Field( + 0.2, title="Shutter Open Fraction" + ) + cameras: bool = Field(False, title="Cameras", section="Show") + clipGhosts: bool = Field(False, title="Clip Ghosts") + deformers: bool = Field(False, title="Deformers") + dimensions: bool = Field(False, title="Dimensions") + dynamicConstraints: bool = Field(False, title="Dynamic Constraints") + dynamics: bool = Field(False, title="Dynamics") + fluids: bool = Field(False, title="Fluids") + follicles: bool = Field(False, title="Follicles") + greasePencils: bool = Field(False, title="Grease Pencils") + grid: bool = Field(False, title="Grid") + hairSystems: bool = Field(True, title="Hair Systems") + handles: bool = Field(False, title="Handles") + headsUpDisplay: bool = Field(False, title="HUD") + ikHandles: bool = Field(False, title="IK Handles") + imagePlane: bool = Field(True, title="Image Plane") + joints: bool = Field(False, title="Joints") + lights: bool = Field(False, title="Lights") + locators: bool = Field(False, title="Locators") + manipulators: bool = Field(False, title="Manipulators") + motionTrails: bool = Field(False, title="Motion Trails") + nCloths: bool = Field(False, title="nCloths") + nParticles: bool = Field(False, title="nParticles") + nRigids: bool = Field(False, title="nRigids") + controlVertices: bool = Field(False, title="NURBS CVs") + nurbsCurves: bool = Field(False, title="NURBS Curves") + hulls: bool = Field(False, title="NURBS Hulls") + nurbsSurfaces: bool = Field(False, title="NURBS Surfaces") + particleInstancers: bool = Field(False, title="Particle Instancers") + pivots: bool = Field(False, title="Pivots") + planes: bool = Field(False, title="Planes") + pluginShapes: bool = Field(False, title="Plugin Shapes") + polymeshes: bool = Field(True, title="Polygons") + strokes: bool = Field(False, title="Strokes") + subdivSurfaces: bool = Field(False, title="Subdiv Surfaces") + textures: bool = Field(False, title="Texture Placements") + pluginObjects: list[PluginObjectsModel] = Field( + default_factory=plugin_objects_default, + title="Plugin Objects" + ) + + @validator("pluginObjects") + def validate_unique_plugin_objects(cls, value): + ensure_unique_names(value) + return value + + +class CameraOptionsSetting(BaseSettingsModel): + displayGateMask: bool = Field(False, title="Display Gate Mask") + displayResolution: bool = Field(False, title="Display Resolution") + displayFilmGate: bool = Field(False, title="Display Film Gate") + displayFieldChart: bool = Field(False, title="Display Field Chart") + displaySafeAction: bool = Field(False, title="Display Safe Action") + displaySafeTitle: bool = Field(False, title="Display Safe Title") + displayFilmPivot: bool = Field(False, title="Display Film Pivot") + displayFilmOrigin: bool = Field(False, title="Display Film Origin") + overscan: int = Field(1.0, title="Overscan") + + +class CapturePresetSetting(BaseSettingsModel): + Codec: CodecSetting = Field( + default_factory=CodecSetting, + title="Codec", + section="Codec") + DisplayOptions: DisplayOptionsSetting = Field( + default_factory=DisplayOptionsSetting, + title="Display Options", + section="Display Options") + Generic: GenericSetting = Field( + default_factory=GenericSetting, + title="Generic", + section="Generic") + Renderer: RendererSetting = Field( + default_factory=RendererSetting, + title="Renderer", + section="Renderer") + Resolution: ResolutionSetting = Field( + default_factory=ResolutionSetting, + title="Resolution", + section="Resolution") + ViewportOptions: ViewportOptionsSetting = Field( + default_factory=ViewportOptionsSetting, + title="Viewport Options") + CameraOptions: CameraOptionsSetting = Field( + default_factory=CameraOptionsSetting, + title="Camera Options") + + +class ProfilesModel(BaseSettingsModel): + _layout = "expanded" + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field(default_factory=list, title="Task names") + product_names: list[str] = Field(default_factory=list, title="Products names") + capture_preset: CapturePresetSetting = Field( + default_factory=CapturePresetSetting, + title="Capture Preset" + ) + + +class ExtractPlayblastSetting(BaseSettingsModel): + capture_preset: CapturePresetSetting = Field( + default_factory=CapturePresetSetting, + title="DEPRECATED! Please use \"Profiles\" below. Capture Preset" + ) + profiles: list[ProfilesModel] = Field( + default_factory=list, + title="Profiles" + ) + + +DEFAULT_PLAYBLAST_SETTING = { + "capture_preset": { + "Codec": { + "compression": "png", + "format": "image", + "quality": 95 + }, + "DisplayOptions": { + "override_display": True, + "background": [ + 125, + 125, + 125, + 1.0 + ], + "backgroundBottom": [ + 125, + 125, + 125, + 1.0 + ], + "backgroundTop": [ + 125, + 125, + 125, + 1.0 + ], + "displayGradient": True + }, + "Generic": { + "isolate_view": True, + "off_screen": True, + "pan_zoom": False + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "width": 1920, + "height": 1080 + }, + "ViewportOptions": { + "override_viewport_options": True, + "displayLights": "default", + "displayTextures": True, + "textureMaxResolution": 1024, + "renderDepthOfField": True, + "shadows": True, + "twoSidedLighting": True, + "lineAAEnable": True, + "multiSample": 8, + "useDefaultMaterial": False, + "wireframeOnShaded": False, + "xray": False, + "jointXray": False, + "backfaceCulling": False, + "ssaoEnable": False, + "ssaoAmount": 1, + "ssaoRadius": 16, + "ssaoFilterRadius": 16, + "ssaoSamples": 16, + "fogging": False, + "hwFogFalloff": "0", + "hwFogDensity": 0.0, + "hwFogStart": 0, + "hwFogEnd": 100, + "hwFogAlpha": 0, + "hwFogColorR": 1.0, + "hwFogColorG": 1.0, + "hwFogColorB": 1.0, + "motionBlurEnable": False, + "motionBlurSampleCount": 8, + "motionBlurShutterOpenFraction": 0.2, + "cameras": False, + "clipGhosts": False, + "deformers": False, + "dimensions": False, + "dynamicConstraints": False, + "dynamics": False, + "fluids": False, + "follicles": False, + "greasePencils": False, + "grid": False, + "hairSystems": True, + "handles": False, + "headsUpDisplay": False, + "ikHandles": False, + "imagePlane": True, + "joints": False, + "lights": False, + "locators": False, + "manipulators": False, + "motionTrails": False, + "nCloths": False, + "nParticles": False, + "nRigids": False, + "controlVertices": False, + "nurbsCurves": False, + "hulls": False, + "nurbsSurfaces": False, + "particleInstancers": False, + "pivots": False, + "planes": False, + "pluginShapes": False, + "polymeshes": True, + "strokes": False, + "subdivSurfaces": False, + "textures": False, + "pluginObjects": [ + { + "name": "gpuCacheDisplayFilter", + "value": False + } + ] + }, + "CameraOptions": { + "displayGateMask": False, + "displayResolution": False, + "displayFilmGate": False, + "displayFieldChart": False, + "displaySafeAction": False, + "displaySafeTitle": False, + "displayFilmPivot": False, + "displayFilmOrigin": False, + "overscan": 1.0 + } + }, + "profiles": [] +} diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py new file mode 100644 index 0000000000..bd7ccdf4d5 --- /dev/null +++ b/server_addon/maya/server/settings/publishers.py @@ -0,0 +1,1262 @@ +import json +from pydantic import Field, validator +from ayon_server.settings import ( + BaseSettingsModel, + MultiplatformPathModel, + ensure_unique_names, +) +from ayon_server.exceptions import BadRequestException +from .publish_playblast import ( + ExtractPlayblastSetting, + DEFAULT_PLAYBLAST_SETTING, +) + + +def linear_unit_enum(): + """Get linear units enumerator.""" + return [ + {"label": "mm", "value": "millimeter"}, + {"label": "cm", "value": "centimeter"}, + {"label": "m", "value": "meter"}, + {"label": "km", "value": "kilometer"}, + {"label": "in", "value": "inch"}, + {"label": "ft", "value": "foot"}, + {"label": "yd", "value": "yard"}, + {"label": "mi", "value": "mile"} + ] + + +def angular_unit_enum(): + """Get angular units enumerator.""" + return [ + {"label": "deg", "value": "degree"}, + {"label": "rad", "value": "radian"}, + ] + + +class BasicValidateModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + + +class ValidateMeshUVSetMap1Model(BasicValidateModel): + """Validate model's default uv set exists and is named 'map1'.""" + pass + + +class ValidateNoAnimationModel(BasicValidateModel): + """Ensure no keyframes on nodes in the Instance.""" + pass + + +class ValidateRigOutSetNodeIdsModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateSkinclusterDeformerSet") + optional: bool = Field(title="Optional") + allow_history_only: bool = Field(title="Allow history only") + + +class ValidateModelNameModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + database: bool = Field(title="Use database shader name definitions") + material_file: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Material File", + description=( + "Path to material file defining list of material names to check." + ) + ) + regex: str = Field( + "(.*)_(\\d)*_(?P.*)_(GEO)", + title="Validation regex", + description=( + "Regex for validating name of top level group name. You can use" + " named capturing groups:(?P.*) for Asset name" + ) + ) + top_level_regex: str = Field( + ".*_GRP", + title="Top level group name regex", + description=( + "To check for asset in name so *_some_asset_name_GRP" + " is valid, use:.*?_(?P.*)_GEO" + ) + ) + + +class ValidateModelContentModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + validate_top_group: bool = Field(title="Validate one top group") + + +class ValidateTransformNamingSuffixModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + SUFFIX_NAMING_TABLE: str = Field( + "{}", + title="Suffix Naming Tables", + widget="textarea", + description=( + "Validates transform suffix based on" + " the type of its children shapes." + ) + ) + + @validator("SUFFIX_NAMING_TABLE") + def validate_json(cls, value): + if not value.strip(): + return "{}" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, dict) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "The text can't be parsed as json object" + ) + return value + ALLOW_IF_NOT_IN_SUFFIX_TABLE: bool = Field( + title="Allow if suffix not in table" + ) + + +class CollectMayaRenderModel(BaseSettingsModel): + sync_workfile_version: bool = Field( + title="Sync render version with workfile" + ) + + +class CollectFbxCameraModel(BaseSettingsModel): + enabled: bool = Field(title="CollectFbxCamera") + + +class CollectGLTFModel(BaseSettingsModel): + enabled: bool = Field(title="CollectGLTF") + + +class ValidateFrameRangeModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateFrameRange") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + exclude_product_types: list[str] = Field( + default_factory=list, + title="Exclude product types" + ) + + +class ValidateShaderNameModel(BaseSettingsModel): + """ + Shader name regex can use named capture group asset to validate against current asset name. + """ + enabled: bool = Field(title="ValidateShaderName") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + regex: str = Field("(?P.*)_(.*)_SHD", title="Validation regex") + + +class ValidateAttributesModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateAttributes") + attributes: str = Field( + "{}", title="Attributes", widget="textarea") + + @validator("attributes") + def validate_json(cls, value): + if not value.strip(): + return "{}" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, dict) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "The attibutes can't be parsed as json object" + ) + return value + + +class ValidateLoadedPluginModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateLoadedPlugin") + optional: bool = Field(title="Optional") + whitelist_native_plugins: bool = Field( + title="Whitelist Maya Native Plugins" + ) + authorized_plugins: list[str] = Field( + default_factory=list, title="Authorized plugins" + ) + + +class ValidateMayaUnitsModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateMayaUnits") + optional: bool = Field(title="Optional") + validate_linear_units: bool = Field(title="Validate linear units") + linear_units: str = Field( + enum_resolver=linear_unit_enum, title="Linear Units" + ) + validate_angular_units: bool = Field(title="Validate angular units") + angular_units: str = Field( + enum_resolver=angular_unit_enum, title="Angular units" + ) + validate_fps: bool = Field(title="Validate fps") + + +class ValidateUnrealStaticMeshNameModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateUnrealStaticMeshName") + optional: bool = Field(title="Optional") + validate_mesh: bool = Field(title="Validate mesh names") + validate_collision: bool = Field(title="Validate collison names") + + +class ValidateCycleErrorModel(BaseSettingsModel): + enabled: bool = Field(title="ValidateCycleError") + optional: bool = Field(title="Optional") + families: list[str] = Field(default_factory=list, title="Families") + + +class ValidatePluginPathAttributesAttrModel(BaseSettingsModel): + name: str = Field(title="Node type") + value: str = Field(title="Attribute") + + +class ValidatePluginPathAttributesModel(BaseSettingsModel): + """Fill in the node types and attributes you want to validate. + +

e.g. AlembicNode.abc_file, the node type is AlembicNode + and the node attribute is abc_file + """ + + enabled: bool = True + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + attribute: list[ValidatePluginPathAttributesAttrModel] = Field( + default_factory=list, + title="File Attribute" + ) + + @validator("attribute") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +# Validate Render Setting +class RendererAttributesModel(BaseSettingsModel): + _layout = "compact" + type: str = Field(title="Type") + value: str = Field(title="Value") + + +class ValidateRenderSettingsModel(BaseSettingsModel): + arnold_render_attributes: list[RendererAttributesModel] = Field( + default_factory=list, title="Arnold Render Attributes") + vray_render_attributes: list[RendererAttributesModel] = Field( + default_factory=list, title="VRay Render Attributes") + redshift_render_attributes: list[RendererAttributesModel] = Field( + default_factory=list, title="Redshift Render Attributes") + renderman_render_attributes: list[RendererAttributesModel] = Field( + default_factory=list, title="Renderman Render Attributes") + + +class BasicValidateModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + + +class ValidateCameraContentsModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + validate_shapes: bool = Field(title="Validate presence of shapes") + + +class ExtractProxyAlembicModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + families: list[str] = Field( + default_factory=list, + title="Families") + + +class ExtractAlembicModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + families: list[str] = Field( + default_factory=list, + title="Families") + + +class ExtractObjModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + + +class ExtractMayaSceneRawModel(BaseSettingsModel): + """Add loaded instances to those published families:""" + enabled: bool = Field(title="ExtractMayaSceneRaw") + add_for_families: list[str] = Field(default_factory=list, title="Families") + + +class ExtractCameraAlembicModel(BaseSettingsModel): + """ + List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax. + """ + enabled: bool = Field(title="ExtractCameraAlembic") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + bake_attributes: str = Field( + "[]", title="Base Attributes", widget="textarea" + ) + + @validator("bake_attributes") + def validate_json_list(cls, value): + if not value.strip(): + return "[]" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, list) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "The text can't be parsed as json object" + ) + return value + + +class ExtractGLBModel(BaseSettingsModel): + enabled: bool = True + active: bool = Field(title="Active") + ogsfx_path: str = Field(title="GLSL Shader Directory") + + +class ExtractLookArgsModel(BaseSettingsModel): + argument: str = Field(title="Argument") + parameters: list[str] = Field(default_factory=list, title="Parameters") + + +class ExtractLookModel(BaseSettingsModel): + maketx_arguments: list[ExtractLookArgsModel] = Field( + default_factory=list, + title="Extra arguments for maketx command line" + ) + + +class ExtractGPUCacheModel(BaseSettingsModel): + enabled: bool = True + families: list[str] = Field(default_factory=list, title="Families") + step: float = Field(1.0, ge=1.0, title="Step") + stepSave: int = Field(1, ge=1, title="Step Save") + optimize: bool = Field(title="Optimize Hierarchy") + optimizationThreshold: int = Field(1, ge=1, title="Optimization Threshold") + optimizeAnimationsForMotionBlur: bool = Field( + title="Optimize Animations For Motion Blur" + ) + writeMaterials: bool = Field(title="Write Materials") + useBaseTessellation: bool = Field(title="User Base Tesselation") + + +class PublishersModel(BaseSettingsModel): + CollectMayaRender: CollectMayaRenderModel = Field( + default_factory=CollectMayaRenderModel, + title="Collect Render Layers", + section="Collectors" + ) + CollectFbxCamera: CollectFbxCameraModel = Field( + default_factory=CollectFbxCameraModel, + title="Collect Camera for FBX export", + ) + CollectGLTF: CollectGLTFModel = Field( + default_factory=CollectGLTFModel, + title="Collect Assets for GLB/GLTF export" + ) + ValidateInstanceInContext: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Instance In Context", + section="Validators" + ) + ValidateContainers: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Containers" + ) + ValidateFrameRange: ValidateFrameRangeModel = Field( + default_factory=ValidateFrameRangeModel, + title="Validate Frame Range" + ) + ValidateShaderName: ValidateShaderNameModel = Field( + default_factory=ValidateShaderNameModel, + title="Validate Shader Name" + ) + ValidateShadingEngine: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Look Shading Engine Naming" + ) + ValidateMayaColorSpace: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Colorspace" + ) + ValidateAttributes: ValidateAttributesModel = Field( + default_factory=ValidateAttributesModel, + title="Validate Attributes" + ) + ValidateLoadedPlugin: ValidateLoadedPluginModel = Field( + default_factory=ValidateLoadedPluginModel, + title="Validate Loaded Plugin" + ) + ValidateMayaUnits: ValidateMayaUnitsModel = Field( + default_factory=ValidateMayaUnitsModel, + title="Validate Maya Units" + ) + ValidateUnrealStaticMeshName: ValidateUnrealStaticMeshNameModel = Field( + default_factory=ValidateUnrealStaticMeshNameModel, + title="Validate Unreal Static Mesh Name" + ) + ValidateCycleError: ValidateCycleErrorModel = Field( + default_factory=ValidateCycleErrorModel, + title="Validate Cycle Error" + ) + ValidatePluginPathAttributes: ValidatePluginPathAttributesModel = Field( + default_factory=ValidatePluginPathAttributesModel, + title="Plug-in Path Attributes" + ) + ValidateRenderSettings: ValidateRenderSettingsModel = Field( + default_factory=ValidateRenderSettingsModel, + title="Validate Render Settings" + ) + ValidateCurrentRenderLayerIsRenderable: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Current Render Layer Has Renderable Camera" + ) + ValidateGLSLMaterial: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate GLSL Material" + ) + ValidateGLSLPlugin: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate GLSL Plugin" + ) + ValidateRenderImageRule: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Render Image Rule (Workspace)" + ) + ValidateRenderNoDefaultCameras: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate No Default Cameras Renderable" + ) + ValidateRenderSingleCamera: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Render Single Camera " + ) + ValidateRenderLayerAOVs: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Render Passes/AOVs Are Registered" + ) + ValidateStepSize: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Step Size" + ) + ValidateVRayDistributedRendering: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="VRay Distributed Rendering" + ) + ValidateVrayReferencedAOVs: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="VRay Referenced AOVs" + ) + ValidateVRayTranslatorEnabled: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="VRay Translator Settings" + ) + ValidateVrayProxy: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="VRay Proxy Settings" + ) + ValidateVrayProxyMembers: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="VRay Proxy Members" + ) + ValidateYetiRenderScriptCallbacks: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Yeti Render Script Callbacks" + ) + ValidateYetiRigCacheState: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Yeti Rig Cache State" + ) + ValidateYetiRigInputShapesInInstance: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Yeti Rig Input Shapes In Instance" + ) + ValidateYetiRigSettings: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Yeti Rig Settings" + ) + # Model - START + ValidateModelName: ValidateModelNameModel = Field( + default_factory=ValidateModelNameModel, + title="Validate Model Name", + section="Model", + ) + ValidateModelContent: ValidateModelContentModel = Field( + default_factory=ValidateModelContentModel, + title="Validate Model Content", + ) + ValidateTransformNamingSuffix: ValidateTransformNamingSuffixModel = Field( + default_factory=ValidateTransformNamingSuffixModel, + title="Validate Transform Naming Suffix", + ) + ValidateColorSets: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Color Sets", + ) + ValidateMeshHasOverlappingUVs: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Has Overlapping UVs", + ) + ValidateMeshArnoldAttributes: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Arnold Attributes", + ) + ValidateMeshShaderConnections: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Shader Connections", + ) + ValidateMeshSingleUVSet: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Single UV Set", + ) + ValidateMeshHasUVs: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Has UVs", + ) + ValidateMeshLaminaFaces: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Lamina Faces", + ) + ValidateMeshNgons: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Ngons", + ) + ValidateMeshNonManifold: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Non-Manifold", + ) + ValidateMeshNoNegativeScale: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh No Negative Scale", + ) + ValidateMeshNonZeroEdgeLength: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Edge Length Non Zero", + ) + ValidateMeshNormalsUnlocked: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Normals Unlocked", + ) + ValidateMeshUVSetMap1: ValidateMeshUVSetMap1Model = Field( + default_factory=ValidateMeshUVSetMap1Model, + title="Validate Mesh UV Set Map 1", + ) + ValidateMeshVerticesHaveEdges: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Mesh Vertices Have Edges", + ) + ValidateNoAnimation: ValidateNoAnimationModel = Field( + default_factory=ValidateNoAnimationModel, + title="Validate No Animation", + ) + ValidateNoNamespace: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate No Namespace", + ) + ValidateNoNullTransforms: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate No Null Transforms", + ) + ValidateNoUnknownNodes: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate No Unknown Nodes", + ) + ValidateNodeNoGhosting: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Node No Ghosting", + ) + ValidateShapeDefaultNames: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Shape Default Names", + ) + ValidateShapeRenderStats: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Shape Render Stats", + ) + ValidateShapeZero: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Shape Zero", + ) + ValidateTransformZero: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Transform Zero", + ) + ValidateUniqueNames: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Unique Names", + ) + ValidateNoVRayMesh: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate No V-Ray Proxies (VRayMesh)", + ) + ValidateUnrealMeshTriangulated: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate if Mesh is Triangulated", + ) + ValidateAlembicVisibleOnly: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Alembic Visible Node", + ) + ExtractProxyAlembic: ExtractProxyAlembicModel = Field( + default_factory=ExtractProxyAlembicModel, + title="Extract Proxy Alembic", + section="Model Extractors", + ) + ExtractAlembic: ExtractAlembicModel = Field( + default_factory=ExtractAlembicModel, + title="Extract Alembic", + ) + ExtractObj: ExtractObjModel = Field( + default_factory=ExtractObjModel, + title="Extract OBJ" + ) + # Model - END + + # Rig - START + ValidateRigContents: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Rig Contents", + section="Rig", + ) + ValidateRigJointsHidden: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Rig Joints Hidden", + ) + ValidateRigControllers: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Rig Controllers", + ) + ValidateAnimationContent: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Animation Content", + ) + ValidateOutRelatedNodeIds: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Animation Out Set Related Node Ids", + ) + ValidateRigControllersArnoldAttributes: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Rig Controllers (Arnold Attributes)", + ) + ValidateSkeletalMeshHierarchy: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Skeletal Mesh Top Node", + ) + ValidateSkinclusterDeformerSet: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Skincluster Deformer Relationships", + ) + ValidateRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field( + default_factory=ValidateRigOutSetNodeIdsModel, + title="Validate Rig Out Set Node Ids", + ) + # Rig - END + ValidateCameraAttributes: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Camera Attributes" + ) + ValidateAssemblyName: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Assembly Name" + ) + ValidateAssemblyNamespaces: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Assembly Namespaces" + ) + ValidateAssemblyModelTransforms: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Assembly Model Transforms" + ) + ValidateAssRelativePaths: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Ass Relative Paths" + ) + ValidateInstancerContent: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Instancer Content" + ) + ValidateInstancerFrameRanges: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Instancer Cache Frame Ranges" + ) + ValidateNoDefaultCameras: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate No Default Cameras" + ) + ValidateUnrealUpAxis: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Unreal Up-Axis Check" + ) + ValidateCameraContents: ValidateCameraContentsModel = Field( + default_factory=ValidateCameraContentsModel, + title="Validate Camera Content" + ) + ExtractPlayblast: ExtractPlayblastSetting = Field( + default_factory=ExtractPlayblastSetting, + title="Extract Playblast Settings", + section="Extractors" + ) + ExtractMayaSceneRaw: ExtractMayaSceneRawModel = Field( + default_factory=ExtractMayaSceneRawModel, + title="Maya Scene(Raw)" + ) + ExtractCameraAlembic: ExtractCameraAlembicModel = Field( + default_factory=ExtractCameraAlembicModel, + title="Extract Camera Alembic" + ) + ExtractGLB: ExtractGLBModel = Field( + default_factory=ExtractGLBModel, + title="Extract GLB" + ) + ExtractLook: ExtractLookModel = Field( + default_factory=ExtractLookModel, + title="Extract Look" + ) + ExtractGPUCache: ExtractGPUCacheModel = Field( + default_factory=ExtractGPUCacheModel, + title="Extract GPU Cache", + ) + + +DEFAULT_SUFFIX_NAMING = { + "mesh": ["_GEO", "_GES", "_GEP", "_OSD"], + "nurbsCurve": ["_CRV"], + "nurbsSurface": ["_NRB"], + "locator": ["_LOC"], + "group": ["_GRP"] +} + +DEFAULT_PUBLISH_SETTINGS = { + "CollectMayaRender": { + "sync_workfile_version": False + }, + "CollectFbxCamera": { + "enabled": False + }, + "CollectGLTF": { + "enabled": False + }, + "ValidateInstanceInContext": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateContainers": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateFrameRange": { + "enabled": True, + "optional": True, + "active": True, + "exclude_product_types": [ + "model", + "rig", + "staticMesh" + ] + }, + "ValidateShaderName": { + "enabled": False, + "optional": True, + "active": True, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateShadingEngine": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMayaColorSpace": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateAttributes": { + "enabled": False, + "attributes": "{}" + }, + "ValidateLoadedPlugin": { + "enabled": False, + "optional": True, + "whitelist_native_plugins": False, + "authorized_plugins": [] + }, + "ValidateMayaUnits": { + "enabled": True, + "optional": False, + "validate_linear_units": True, + "linear_units": "cm", + "validate_angular_units": True, + "angular_units": "deg", + "validate_fps": True + }, + "ValidateUnrealStaticMeshName": { + "enabled": True, + "optional": True, + "validate_mesh": False, + "validate_collision": True + }, + "ValidateCycleError": { + "enabled": True, + "optional": False, + "families": [ + "rig" + ] + }, + "ValidatePluginPathAttributes": { + "enabled": True, + "optional": False, + "active": True, + "attribute": [ + {"name": "AlembicNode", "value": "abc_File"}, + {"name": "VRayProxy", "value": "fileName"}, + {"name": "RenderManArchive", "value": "filename"}, + {"name": "pgYetiMaya", "value": "cacheFileName"}, + {"name": "aiStandIn", "value": "dso"}, + {"name": "RedshiftSprite", "value": "tex0"}, + {"name": "RedshiftBokeh", "value": "dofBokehImage"}, + {"name": "RedshiftCameraMap", "value": "tex0"}, + {"name": "RedshiftEnvironment", "value": "tex2"}, + {"name": "RedshiftDomeLight", "value": "tex1"}, + {"name": "RedshiftIESLight", "value": "profile"}, + {"name": "RedshiftLightGobo", "value": "tex0"}, + {"name": "RedshiftNormalMap", "value": "tex0"}, + {"name": "RedshiftProxyMesh", "value": "fileName"}, + {"name": "RedshiftVolumeShape", "value": "fileName"}, + {"name": "VRayTexGLSL", "value": "fileName"}, + {"name": "VRayMtlGLSL", "value": "fileName"}, + {"name": "VRayVRmatMtl", "value": "fileName"}, + {"name": "VRayPtex", "value": "ptexFile"}, + {"name": "VRayLightIESShape", "value": "iesFile"}, + {"name": "VRayMesh", "value": "materialAssignmentsFile"}, + {"name": "VRayMtlOSL", "value": "fileName"}, + {"name": "VRayTexOSL", "value": "fileName"}, + {"name": "VRayTexOCIO", "value": "ocioConfigFile"}, + {"name": "VRaySettingsNode", "value": "pmap_autoSaveFile2"}, + {"name": "VRayScannedMtl", "value": "file"}, + {"name": "VRayScene", "value": "parameterOverrideFilePath"}, + {"name": "VRayMtlMDL", "value": "filename"}, + {"name": "VRaySimbiont", "value": "file"}, + {"name": "dlOpenVDBShape", "value": "filename"}, + {"name": "pgYetiMayaShape", "value": "liveABCFilename"}, + {"name": "gpuCache", "value": "cacheFileName"}, + ] + }, + "ValidateRenderSettings": { + "arnold_render_attributes": [], + "vray_render_attributes": [], + "redshift_render_attributes": [], + "renderman_render_attributes": [] + }, + "ValidateCurrentRenderLayerIsRenderable": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateGLSLMaterial": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateGLSLPlugin": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateRenderImageRule": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateRenderNoDefaultCameras": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateRenderSingleCamera": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateRenderLayerAOVs": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateStepSize": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateVRayDistributedRendering": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateVrayReferencedAOVs": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateVRayTranslatorEnabled": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateVrayProxy": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateVrayProxyMembers": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateYetiRenderScriptCallbacks": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateYetiRigCacheState": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateYetiRigInputShapesInInstance": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateYetiRigSettings": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateModelName": { + "enabled": False, + "database": True, + "material_file": { + "windows": "", + "darwin": "", + "linux": "" + }, + "regex": "(.*)_(\\d)*_(?P.*)_(GEO)", + "top_level_regex": ".*_GRP" + }, + "ValidateModelContent": { + "enabled": True, + "optional": False, + "validate_top_group": True + }, + "ValidateTransformNamingSuffix": { + "enabled": True, + "optional": True, + "SUFFIX_NAMING_TABLE": json.dumps(DEFAULT_SUFFIX_NAMING, indent=4), + "ALLOW_IF_NOT_IN_SUFFIX_TABLE": True + }, + "ValidateColorSets": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshArnoldAttributes": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshShaderConnections": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshSingleUVSet": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshHasUVs": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshLaminaFaces": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshNgons": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshNonManifold": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshNoNegativeScale": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateMeshNonZeroEdgeLength": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMeshNormalsUnlocked": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshUVSetMap1": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateMeshVerticesHaveEdges": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateNoAnimation": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateNoNamespace": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateNoNullTransforms": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateNoUnknownNodes": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateNodeNoGhosting": { + "enabled": False, + "optional": False, + "active": True + }, + "ValidateShapeDefaultNames": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateShapeRenderStats": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateShapeZero": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateTransformZero": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateUniqueNames": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateNoVRayMesh": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateUnrealMeshTriangulated": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateAlembicVisibleOnly": { + "enabled": True, + "optional": False, + "active": True + }, + "ExtractProxyAlembic": { + "enabled": True, + "families": [ + "proxyAbc" + ] + }, + "ExtractAlembic": { + "enabled": True, + "families": [ + "pointcache", + "model", + "vrayproxy.alembic" + ] + }, + "ExtractObj": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateRigContents": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateRigJointsHidden": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateRigControllers": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateAnimationContent": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateOutRelatedNodeIds": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateRigControllersArnoldAttributes": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateSkeletalMeshHierarchy": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateSkinclusterDeformerSet": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateRigOutSetNodeIds": { + "enabled": True, + "optional": False, + "allow_history_only": False + }, + "ValidateCameraAttributes": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateAssemblyName": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateAssemblyNamespaces": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateAssemblyModelTransforms": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateAssRelativePaths": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateInstancerContent": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateInstancerFrameRanges": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateNoDefaultCameras": { + "enabled": True, + "optional": False, + "active": True + }, + "ValidateUnrealUpAxis": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateCameraContents": { + "enabled": True, + "optional": False, + "validate_shapes": True + }, + "ExtractPlayblast": DEFAULT_PLAYBLAST_SETTING, + "ExtractMayaSceneRaw": { + "enabled": True, + "add_for_families": [ + "layout" + ] + }, + "ExtractCameraAlembic": { + "enabled": True, + "optional": True, + "active": True, + "bake_attributes": "[]" + }, + "ExtractGLB": { + "enabled": True, + "active": True, + "ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx" + }, + "ExtractLook": { + "maketx_arguments": [] + }, + "ExtractGPUCache": { + "enabled": False, + "families": [ + "model", + "animation", + "pointcache" + ], + "step": 1.0, + "stepSave": 1, + "optimize": True, + "optimizationThreshold": 40000, + "optimizeAnimationsForMotionBlur": True, + "writeMaterials": True, + "useBaseTessellation": True + } +} diff --git a/server_addon/maya/server/settings/render_settings.py b/server_addon/maya/server/settings/render_settings.py new file mode 100644 index 0000000000..b6163a04ce --- /dev/null +++ b/server_addon/maya/server/settings/render_settings.py @@ -0,0 +1,500 @@ +"""Providing models and values for Maya Render Settings.""" +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +def aov_separators_enum(): + return [ + {"value": "dash", "label": "- (dash)"}, + {"value": "underscore", "label": "_ (underscore)"}, + {"value": "dot", "label": ". (dot)"} + ] + + +def arnold_image_format_enum(): + """Return enumerator for Arnold output formats.""" + return [ + {"label": "jpeg", "value": "jpeg"}, + {"label": "png", "value": "png"}, + {"label": "deepexr", "value": "deep exr"}, + {"label": "tif", "value": "tif"}, + {"label": "exr", "value": "exr"}, + {"label": "maya", "value": "maya"}, + {"label": "mtoa_shaders", "value": "mtoa_shaders"} + ] + + +def arnold_aov_list_enum(): + """Return enumerator for Arnold AOVs. + + Note: Key is value, Value in this case is Label. This + was taken from v3 settings. + """ + return [ + {"value": "empty", "label": "< empty >"}, + {"value": "ID", "label": "ID"}, + {"value": "N", "label": "N"}, + {"value": "P", "label": "P"}, + {"value": "Pref", "label": "Pref"}, + {"value": "RGBA", "label": "RGBA"}, + {"value": "Z", "label": "Z"}, + {"value": "albedo", "label": "albedo"}, + {"value": "background", "label": "background"}, + {"value": "coat", "label": "coat"}, + {"value": "coat_albedo", "label": "coat_albedo"}, + {"value": "coat_direct", "label": "coat_direct"}, + {"value": "coat_indirect", "label": "coat_indirect"}, + {"value": "cputime", "label": "cputime"}, + {"value": "crypto_asset", "label": "crypto_asset"}, + {"value": "crypto_material", "label": "cypto_material"}, + {"value": "crypto_object", "label": "crypto_object"}, + {"value": "diffuse", "label": "diffuse"}, + {"value": "diffuse_albedo", "label": "diffuse_albedo"}, + {"value": "diffuse_direct", "label": "diffuse_direct"}, + {"value": "diffuse_indirect", "label": "diffuse_indirect"}, + {"value": "direct", "label": "direct"}, + {"value": "emission", "label": "emission"}, + {"value": "highlight", "label": "highlight"}, + {"value": "indirect", "label": "indirect"}, + {"value": "motionvector", "label": "motionvector"}, + {"value": "opacity", "label": "opacity"}, + {"value": "raycount", "label": "raycount"}, + {"value": "rim_light", "label": "rim_light"}, + {"value": "shadow", "label": "shadow"}, + {"value": "shadow_diff", "label": "shadow_diff"}, + {"value": "shadow_mask", "label": "shadow_mask"}, + {"value": "shadow_matte", "label": "shadow_matte"}, + {"value": "sheen", "label": "sheen"}, + {"value": "sheen_albedo", "label": "sheen_albedo"}, + {"value": "sheen_direct", "label": "sheen_direct"}, + {"value": "sheen_indirect", "label": "sheen_indirect"}, + {"value": "specular", "label": "specular"}, + {"value": "specular_albedo", "label": "specular_albedo"}, + {"value": "specular_direct", "label": "specular_direct"}, + {"value": "specular_indirect", "label": "specular_indirect"}, + {"value": "sss", "label": "sss"}, + {"value": "sss_albedo", "label": "sss_albedo"}, + {"value": "sss_direct", "label": "sss_direct"}, + {"value": "sss_indirect", "label": "sss_indirect"}, + {"value": "transmission", "label": "transmission"}, + {"value": "transmission_albedo", "label": "transmission_albedo"}, + {"value": "transmission_direct", "label": "transmission_direct"}, + {"value": "transmission_indirect", "label": "transmission_indirect"}, + {"value": "volume", "label": "volume"}, + {"value": "volume_Z", "label": "volume_Z"}, + {"value": "volume_albedo", "label": "volume_albedo"}, + {"value": "volume_direct", "label": "volume_direct"}, + {"value": "volume_indirect", "label": "volume_indirect"}, + {"value": "volume_opacity", "label": "volume_opacity"}, + ] + + +def vray_image_output_enum(): + """Return output format for Vray enumerator.""" + return [ + {"label": "png", "value": "png"}, + {"label": "jpg", "value": "jpg"}, + {"label": "vrimg", "value": "vrimg"}, + {"label": "hdr", "value": "hdr"}, + {"label": "exr", "value": "exr"}, + {"label": "exr (multichannel)", "value": "exr (multichannel)"}, + {"label": "exr (deep)", "value": "exr (deep)"}, + {"label": "tga", "value": "tga"}, + {"label": "bmp", "value": "bmp"}, + {"label": "sgi", "value": "sgi"} + ] + + +def vray_aov_list_enum(): + """Return enumerator for Vray AOVs. + + Note: Key is value, Value in this case is Label. This + was taken from v3 settings. + """ + + return [ + {"value": "empty", "label": "< empty >"}, + {"value": "atmosphereChannel", "label": "atmosphere"}, + {"value": "backgroundChannel", "label": "background"}, + {"value": "bumpNormalsChannel", "label": "bumpnormals"}, + {"value": "causticsChannel", "label": "caustics"}, + {"value": "coatFilterChannel", "label": "coat_filter"}, + {"value": "coatGlossinessChannel", "label": "coatGloss"}, + {"value": "coatReflectionChannel", "label": "coat_reflection"}, + {"value": "vrayCoatChannel", "label": "coat_specular"}, + {"value": "CoverageChannel", "label": "coverage"}, + {"value": "cryptomatteChannel", "label": "cryptomatte"}, + {"value": "customColor", "label": "custom_color"}, + {"value": "drBucketChannel", "label": "DR"}, + {"value": "denoiserChannel", "label": "denoiser"}, + {"value": "diffuseChannel", "label": "diffuse"}, + {"value": "ExtraTexElement", "label": "extraTex"}, + {"value": "giChannel", "label": "GI"}, + {"value": "LightMixElement", "label": "None"}, + {"value": "lightingChannel", "label": "lighting"}, + {"value": "LightingAnalysisChannel", "label": "LightingAnalysis"}, + {"value": "materialIDChannel", "label": "materialID"}, + {"value": "MaterialSelectElement", "label": "materialSelect"}, + {"value": "matteShadowChannel", "label": "matteShadow"}, + {"value": "MultiMatteElement", "label": "multimatte"}, + {"value": "multimatteIDChannel", "label": "multimatteID"}, + {"value": "normalsChannel", "label": "normals"}, + {"value": "nodeIDChannel", "label": "objectId"}, + {"value": "objectSelectChannel", "label": "objectSelect"}, + {"value": "rawCoatFilterChannel", "label": "raw_coat_filter"}, + {"value": "rawCoatReflectionChannel", "label": "raw_coat_reflection"}, + {"value": "rawDiffuseFilterChannel", "label": "rawDiffuseFilter"}, + {"value": "rawGiChannel", "label": "rawGI"}, + {"value": "rawLightChannel", "label": "rawLight"}, + {"value": "rawReflectionChannel", "label": "rawReflection"}, + { + "value": "rawReflectionFilterChannel", + "label": "rawReflectionFilter" + }, + {"value": "rawRefractionChannel", "label": "rawRefraction"}, + { + "value": "rawRefractionFilterChannel", + "label": "rawRefractionFilter" + }, + {"value": "rawShadowChannel", "label": "rawShadow"}, + {"value": "rawSheenFilterChannel", "label": "raw_sheen_filter"}, + { + "value": "rawSheenReflectionChannel", + "label": "raw_sheen_reflection" + }, + {"value": "rawTotalLightChannel", "label": "rawTotalLight"}, + {"value": "reflectIORChannel", "label": "reflIOR"}, + {"value": "reflectChannel", "label": "reflect"}, + {"value": "reflectionFilterChannel", "label": "reflectionFilter"}, + {"value": "reflectGlossinessChannel", "label": "reflGloss"}, + {"value": "refractChannel", "label": "refract"}, + {"value": "refractionFilterChannel", "label": "refractionFilter"}, + {"value": "refractGlossinessChannel", "label": "refrGloss"}, + {"value": "renderIDChannel", "label": "renderId"}, + {"value": "FastSSS2Channel", "label": "SSS"}, + {"value": "sampleRateChannel", "label": "sampleRate"}, + {"value": "samplerInfo", "label": "samplerInfo"}, + {"value": "selfIllumChannel", "label": "selfIllum"}, + {"value": "shadowChannel", "label": "shadow"}, + {"value": "sheenFilterChannel", "label": "sheen_filter"}, + {"value": "sheenGlossinessChannel", "label": "sheenGloss"}, + {"value": "sheenReflectionChannel", "label": "sheen_reflection"}, + {"value": "vraySheenChannel", "label": "sheen_specular"}, + {"value": "specularChannel", "label": "specular"}, + {"value": "Toon", "label": "Toon"}, + {"value": "toonLightingChannel", "label": "toonLighting"}, + {"value": "toonSpecularChannel", "label": "toonSpecular"}, + {"value": "totalLightChannel", "label": "totalLight"}, + {"value": "unclampedColorChannel", "label": "unclampedColor"}, + {"value": "VRScansPaintMaskChannel", "label": "VRScansPaintMask"}, + {"value": "VRScansZoneMaskChannel", "label": "VRScansZoneMask"}, + {"value": "velocityChannel", "label": "velocity"}, + {"value": "zdepthChannel", "label": "zDepth"}, + {"value": "LightSelectElement", "label": "lightselect"}, + ] + + +def redshift_engine_enum(): + """Get Redshift engine type enumerator.""" + return [ + {"value": "0", "label": "None"}, + {"value": "1", "label": "Photon Map"}, + {"value": "2", "label": "Irradiance Cache"}, + {"value": "3", "label": "Brute Force"} + ] + + +def redshift_image_output_enum(): + """Return output format for Redshift enumerator.""" + return [ + {"value": "iff", "label": "Maya IFF"}, + {"value": "exr", "label": "OpenEXR"}, + {"value": "tif", "label": "TIFF"}, + {"value": "png", "label": "PNG"}, + {"value": "tga", "label": "Targa"}, + {"value": "jpg", "label": "JPEG"} + ] + + +def redshift_aov_list_enum(): + """Return enumerator for Vray AOVs. + + Note: Key is value, Value in this case is Label. This + was taken from v3 settings. + """ + return [ + {"value": "empty", "label": "< none >"}, + {"value": "AO", "label": "Ambient Occlusion"}, + {"value": "Background", "label": "Background"}, + {"value": "Beauty", "label": "Beauty"}, + {"value": "BumpNormals", "label": "Bump Normals"}, + {"value": "Caustics", "label": "Caustics"}, + {"value": "CausticsRaw", "label": "Caustics Raw"}, + {"value": "Cryptomatte", "label": "Cryptomatte"}, + {"value": "Custom", "label": "Custom"}, + {"value": "Z", "label": "Depth"}, + {"value": "DiffuseFilter", "label": "Diffuse Filter"}, + {"value": "DiffuseLighting", "label": "Diffuse Lighting"}, + {"value": "DiffuseLightingRaw", "label": "Diffuse Lighting Raw"}, + {"value": "Emission", "label": "Emission"}, + {"value": "GI", "label": "Global Illumination"}, + {"value": "GIRaw", "label": "Global Illumination Raw"}, + {"value": "Matte", "label": "Matte"}, + {"value": "MotionVectors", "label": "Ambient Occlusion"}, + {"value": "N", "label": "Normals"}, + {"value": "ID", "label": "ObjectID"}, + {"value": "ObjectBumpNormal", "label": "Object-Space Bump Normals"}, + {"value": "ObjectPosition", "label": "Object-Space Positions"}, + {"value": "PuzzleMatte", "label": "Puzzle Matte"}, + {"value": "Reflections", "label": "Reflections"}, + {"value": "ReflectionsFilter", "label": "Reflections Filter"}, + {"value": "ReflectionsRaw", "label": "Reflections Raw"}, + {"value": "Refractions", "label": "Refractions"}, + {"value": "RefractionsFilter", "label": "Refractions Filter"}, + {"value": "RefractionsRaw", "label": "Refractions Filter"}, + {"value": "Shadows", "label": "Shadows"}, + {"value": "SpecularLighting", "label": "Specular Lighting"}, + {"value": "SSS", "label": "Sub Surface Scatter"}, + {"value": "SSSRaw", "label": "Sub Surface Scatter Raw"}, + { + "value": "TotalDiffuseLightingRaw", + "label": "Total Diffuse Lighting Raw" + }, + { + "value": "TotalTransLightingRaw", + "label": "Total Translucency Filter" + }, + {"value": "TransTint", "label": "Translucency Filter"}, + {"value": "TransGIRaw", "label": "Translucency Lighting Raw"}, + {"value": "VolumeFogEmission", "label": "Volume Fog Emission"}, + {"value": "VolumeFogTint", "label": "Volume Fog Tint"}, + {"value": "VolumeLighting", "label": "Volume Lighting"}, + {"value": "P", "label": "World Position"}, + ] + + +class AdditionalOptionsModel(BaseSettingsModel): + """Additional Option""" + _layout = "compact" + + attribute: str = Field("", title="Attribute name") + value: str = Field("", title="Value") + + +class ArnoldSettingsModel(BaseSettingsModel): + image_prefix: str = Field(title="Image prefix template") + image_format: str = Field( + enum_resolver=arnold_image_format_enum, title="Output Image Format") + multilayer_exr: bool = Field(title="Multilayer (exr)") + tiled: bool = Field(title="Tiled (tif, exr)") + aov_list: list[str] = Field( + default_factory=list, + enum_resolver=arnold_aov_list_enum, + title="AOVs to create" + ) + additional_options: list[AdditionalOptionsModel] = Field( + default_factory=list, + title="Additional Arnold Options", + description=( + "Add additional options - put attribute and value, like AASamples" + ) + ) + + +class VraySettingsModel(BaseSettingsModel): + image_prefix: str = Field(title="Image prefix template") + # engine was str because of JSON limitation (key must be string) + engine: str = Field( + enum_resolver=lambda: [ + {"label": "V-Ray", "value": "1"}, + {"label": "V-Ray GPU", "value": "2"} + ], + title="Production Engine" + ) + image_format: str = Field( + enum_resolver=vray_image_output_enum, + title="Output Image Format" + ) + aov_list: list[str] = Field( + default_factory=list, + enum_resolver=vray_aov_list_enum, + title="AOVs to create" + ) + additional_options: list[AdditionalOptionsModel] = Field( + default_factory=list, + title="Additional Vray Options", + description=( + "Add additional options - put attribute and value," + " like aaFilterSize" + ) + ) + + +class RedshiftSettingsModel(BaseSettingsModel): + image_prefix: str = Field(title="Image prefix template") + # both engines are using the same enumerator, + # both were originally str because of JSON limitation. + primary_gi_engine: str = Field( + enum_resolver=redshift_engine_enum, + title="Primary GI Engine" + ) + secondary_gi_engine: str = Field( + enum_resolver=redshift_engine_enum, + title="Secondary GI Engine" + ) + image_format: str = Field( + enum_resolver=redshift_image_output_enum, + title="Output Image Format" + ) + multilayer_exr: bool = Field(title="Multilayer (exr)") + force_combine: bool = Field(title="Force combine beauty and AOVs") + aov_list: list[str] = Field( + default_factory=list, + enum_resolver=redshift_aov_list_enum, + title="AOVs to create" + ) + additional_options: list[AdditionalOptionsModel] = Field( + default_factory=list, + title="Additional Vray Options", + description=( + "Add additional options - put attribute and value," + " like reflectionMaxTraceDepth" + ) + ) + + +def renderman_display_filters(): + return [ + "PxrBackgroundDisplayFilter", + "PxrCopyAOVDisplayFilter", + "PxrEdgeDetect", + "PxrFilmicTonemapperDisplayFilter", + "PxrGradeDisplayFilter", + "PxrHalfBufferErrorFilter", + "PxrImageDisplayFilter", + "PxrLightSaturation", + "PxrShadowDisplayFilter", + "PxrStylizedHatching", + "PxrStylizedLines", + "PxrStylizedToon", + "PxrWhitePointDisplayFilter" + ] + + +def renderman_sample_filters_enum(): + return [ + "PxrBackgroundSampleFilter", + "PxrCopyAOVSampleFilter", + "PxrCryptomatte", + "PxrFilmicTonemapperSampleFilter", + "PxrGradeSampleFilter", + "PxrShadowFilter", + "PxrWatermarkFilter", + "PxrWhitePointSampleFilter" + ] + + +class RendermanSettingsModel(BaseSettingsModel): + image_prefix: str = Field( + "", title="Image prefix template") + image_dir: str = Field( + "", title="Image Output Directory") + display_filters: list[str] = Field( + default_factory=list, + title="Display Filters", + enum_resolver=renderman_display_filters + ) + imageDisplay_dir: str = Field( + "", title="Image Display Filter Directory") + sample_filters: list[str] = Field( + default_factory=list, + title="Sample Filters", + enum_resolver=renderman_sample_filters_enum + ) + cryptomatte_dir: str = Field( + "", title="Cryptomatte Output Directory") + watermark_dir: str = Field( + "", title="Watermark Filter Directory") + additional_options: list[AdditionalOptionsModel] = Field( + default_factory=list, + title="Additional Renderer Options" + ) + + +class RenderSettingsModel(BaseSettingsModel): + apply_render_settings: bool = Field( + title="Apply Render Settings on creation" + ) + default_render_image_folder: str = Field( + title="Default render image folder" + ) + enable_all_lights: bool = Field( + title="Include all lights in Render Setup Layers by default" + ) + aov_separator: str = Field( + "underscore", + title="AOV Separator character", + enum_resolver=aov_separators_enum + ) + reset_current_frame: bool = Field( + title="Reset Current Frame") + remove_aovs: bool = Field( + title="Remove existing AOVs") + arnold_renderer: ArnoldSettingsModel = Field( + default_factory=ArnoldSettingsModel, + title="Arnold Renderer") + vray_renderer: VraySettingsModel = Field( + default_factory=VraySettingsModel, + title="Vray Renderer") + redshift_renderer: RedshiftSettingsModel = Field( + default_factory=RedshiftSettingsModel, + title="Redshift Renderer") + renderman_renderer: RendermanSettingsModel = Field( + default_factory=RendermanSettingsModel, + title="Renderman Renderer") + + +DEFAULT_RENDER_SETTINGS = { + "apply_render_settings": True, + "default_render_image_folder": "renders/maya", + "enable_all_lights": True, + "aov_separator": "underscore", + "reset_current_frame": False, + "remove_aovs": False, + "arnold_renderer": { + "image_prefix": "//_", + "image_format": "exr", + "multilayer_exr": True, + "tiled": True, + "aov_list": [], + "additional_options": [] + }, + "vray_renderer": { + "image_prefix": "//", + "engine": "1", + "image_format": "exr", + "aov_list": [], + "additional_options": [] + }, + "redshift_renderer": { + "image_prefix": "//", + "primary_gi_engine": "0", + "secondary_gi_engine": "0", + "image_format": "exr", + "multilayer_exr": True, + "force_combine": True, + "aov_list": [], + "additional_options": [] + }, + "renderman_renderer": { + "image_prefix": "{aov_separator}..", + "image_dir": "/", + "display_filters": [], + "imageDisplay_dir": "/{aov_separator}imageDisplayFilter..", + "sample_filters": [], + "cryptomatte_dir": "/{aov_separator}cryptomatte..", + "watermark_dir": "/{aov_separator}watermarkFilter..", + "additional_options": [] + } +} diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py new file mode 100644 index 0000000000..82c1c2e53c --- /dev/null +++ b/server_addon/maya/server/settings/scriptsmenu.py @@ -0,0 +1,43 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class ScriptsmenuSubmodel(BaseSettingsModel): + """Item Definition""" + _isGroup = True + type: str = Field(title="Type") + command: str = Field(title="Command") + sourcetype: str = Field(title="Source Type") + title: str = Field(title="Title") + tooltip: str = Field(title="Tooltip") + tags: list[str] = Field(default_factory=list, title="A list of tags") + + +class ScriptsmenuModel(BaseSettingsModel): + _isGroup = True + + name: str = Field(title="Menu Name") + definition: list[ScriptsmenuSubmodel] = Field( + default_factory=list, + title="Menu Definition", + description="Scriptmenu Items Definition" + ) + + +DEFAULT_SCRIPTSMENU_SETTINGS = { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "command": "import openpype.hosts.maya.api.commands as op_cmds; op_cmds.edit_shader_definitions()", + "sourcetype": "python", + "title": "Edit shader name definitions", + "tooltip": "Edit shader name definitions used in validation and renaming.", + "tags": [ + "pipeline", + "shader" + ] + } + ] +} diff --git a/server_addon/maya/server/settings/templated_workfile_settings.py b/server_addon/maya/server/settings/templated_workfile_settings.py new file mode 100644 index 0000000000..ef81b31a07 --- /dev/null +++ b/server_addon/maya/server/settings/templated_workfile_settings.py @@ -0,0 +1,25 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel, task_types_enum + + +class WorkfileBuildProfilesModel(BaseSettingsModel): + _layout = "expanded" + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field(default_factory=list, title="Task names") + path: str = Field("", title="Path to template") + + +class TemplatedProfilesModel(BaseSettingsModel): + profiles: list[WorkfileBuildProfilesModel] = Field( + default_factory=list, + title="Profiles" + ) + + +DEFAULT_TEMPLATED_WORKFILE_SETTINGS = { + "profiles": [] +} diff --git a/server_addon/maya/server/settings/workfile_build_settings.py b/server_addon/maya/server/settings/workfile_build_settings.py new file mode 100644 index 0000000000..dc56d1a320 --- /dev/null +++ b/server_addon/maya/server/settings/workfile_build_settings.py @@ -0,0 +1,131 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel, task_types_enum + + +class ContextItemModel(BaseSettingsModel): + _layout = "expanded" + product_name_filters: list[str] = Field( + default_factory=list, title="Product name Filters") + product_types: list[str] = Field( + default_factory=list, title="Product types") + repre_names: list[str] = Field( + default_factory=list, title="Repre Names") + loaders: list[str] = Field( + default_factory=list, title="Loaders") + + +class WorkfileSettingModel(BaseSettingsModel): + _layout = "expanded" + task_types: list[str] = Field( + default_factory=list, + enum_resolver=task_types_enum, + title="Task types") + tasks: list[str] = Field( + default_factory=list, + title="Task names") + current_context: list[ContextItemModel] = Field( + default_factory=list, + title="Current Context") + linked_assets: list[ContextItemModel] = Field( + default_factory=list, + title="Linked Assets") + + +class ProfilesModel(BaseSettingsModel): + profiles: list[WorkfileSettingModel] = Field( + default_factory=list, + title="Profiles" + ) + + +DEFAULT_WORKFILE_SETTING = { + "profiles": [ + { + "task_types": [], + "tasks": [ + "Lighting" + ], + "current_context": [ + { + "product_name_filters": [ + ".+[Mm]ain" + ], + "product_types": [ + "model" + ], + "repre_names": [ + "abc", + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "product_name_filters": [], + "product_types": [ + "animation", + "pointcache", + "proxyAbc" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "product_name_filters": [], + "product_types": [ + "rendersetup" + ], + "repre_names": [ + "json" + ], + "loaders": [ + "RenderSetupLoader" + ] + }, + { + "product_name_filters": [], + "product_types": [ + "camera" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + } + ], + "linked_assets": [ + { + "product_name_filters": [], + "product_types": [ + "sedress" + ], + "repre_names": [ + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "product_name_filters": [], + "product_types": [ + "ArnoldStandin" + ], + "repre_names": [ + "ass" + ], + "loaders": [ + "assLoader" + ] + } + ] + } + ] +} diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py new file mode 100644 index 0000000000..d4b9e2d7f3 --- /dev/null +++ b/server_addon/maya/server/version.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +"""Package declaring addon version.""" +__version__ = "0.1.0" diff --git a/server_addon/muster/server/__init__.py b/server_addon/muster/server/__init__.py new file mode 100644 index 0000000000..2cb8943554 --- /dev/null +++ b/server_addon/muster/server/__init__.py @@ -0,0 +1,17 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import MusterSettings, DEFAULT_VALUES + + +class MusterAddon(BaseServerAddon): + name = "muster" + version = __version__ + title = "Muster" + settings_model: Type[MusterSettings] = MusterSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/muster/server/settings.py b/server_addon/muster/server/settings.py new file mode 100644 index 0000000000..f3f6660abc --- /dev/null +++ b/server_addon/muster/server/settings.py @@ -0,0 +1,37 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class TemplatesMapping(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: int = Field(title="mapping") + + +class MusterSettings(BaseSettingsModel): + enabled: bool = True + MUSTER_REST_URL: str = Field("", title="Muster Rest URL") + + templates_mapping: list[TemplatesMapping] = Field( + default_factory=list, + title="Templates mapping", + ) + + +DEFAULT_VALUES = { + "enabled": False, + "MUSTER_REST_URL": "http://127.0.0.1:9890", + "templates_mapping": [ + {"name": "file_layers", "value": 7}, + {"name": "mentalray", "value": 2}, + {"name": "mentalray_sf", "value": 6}, + {"name": "redshift", "value": 55}, + {"name": "renderman", "value": 29}, + {"name": "software", "value": 1}, + {"name": "software_sf", "value": 5}, + {"name": "turtle", "value": 10}, + {"name": "vector", "value": 4}, + {"name": "vray", "value": 37}, + {"name": "ffmpeg", "value": 48} + ] +} diff --git a/server_addon/muster/server/version.py b/server_addon/muster/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/muster/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/nuke/server/__init__.py b/server_addon/nuke/server/__init__.py new file mode 100644 index 0000000000..032ceea5fb --- /dev/null +++ b/server_addon/nuke/server/__init__.py @@ -0,0 +1,17 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import NukeSettings, DEFAULT_VALUES + + +class NukeAddon(BaseServerAddon): + name = "nuke" + title = "Nuke" + version = __version__ + settings_model: Type[NukeSettings] = NukeSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/nuke/server/settings/__init__.py b/server_addon/nuke/server/settings/__init__.py new file mode 100644 index 0000000000..1e58865395 --- /dev/null +++ b/server_addon/nuke/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + NukeSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "NukeSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/nuke/server/settings/common.py b/server_addon/nuke/server/settings/common.py new file mode 100644 index 0000000000..f1bb46ff90 --- /dev/null +++ b/server_addon/nuke/server/settings/common.py @@ -0,0 +1,128 @@ +import json +from pydantic import Field +from ayon_server.exceptions import BadRequestException +from ayon_server.settings import BaseSettingsModel +from ayon_server.types import ( + ColorRGBA_float, + ColorRGB_uint8 +) + + +def validate_json_dict(value): + if not value.strip(): + return "{}" + try: + converted_value = json.loads(value) + success = isinstance(converted_value, dict) + except json.JSONDecodeError: + success = False + + if not success: + raise BadRequestException( + "Environment's can't be parsed as json object" + ) + return value + + +class Vector2d(BaseSettingsModel): + _layout = "compact" + + x: float = Field(1.0, title="X") + y: float = Field(1.0, title="Y") + + +class Vector3d(BaseSettingsModel): + _layout = "compact" + + x: float = Field(1.0, title="X") + y: float = Field(1.0, title="Y") + z: float = Field(1.0, title="Z") + + +def formatable_knob_type_enum(): + return [ + {"value": "text", "label": "Text"}, + {"value": "number", "label": "Number"}, + {"value": "decimal_number", "label": "Decimal number"}, + {"value": "2d_vector", "label": "2D vector"}, + # "3D vector" + ] + + +class Formatable(BaseSettingsModel): + _layout = "compact" + + template: str = Field( + "", + placeholder="""{{key}} or {{key}};{{key}}""", + title="Template" + ) + to_type: str = Field( + "Text", + title="To Knob type", + enum_resolver=formatable_knob_type_enum, + ) + + +knob_types_enum = [ + {"value": "text", "label": "Text"}, + {"value": "formatable", "label": "Formate from template"}, + {"value": "color_gui", "label": "Color GUI"}, + {"value": "boolean", "label": "Boolean"}, + {"value": "number", "label": "Number"}, + {"value": "decimal_number", "label": "Decimal number"}, + {"value": "vector_2d", "label": "2D vector"}, + {"value": "vector_3d", "label": "3D vector"}, + {"value": "color", "label": "Color"}, + {"value": "expression", "label": "Expression"} +] + + +class KnobModel(BaseSettingsModel): + """# TODO: new data structure + - v3 was having type, name, value but + ayon is not able to make it the same. Current model is + defining `type` as `text` and instead of `value` the key is `text`. + So if `type` is `boolean` then key is `boolean` (value). + """ + _layout = "expanded" + + type: str = Field( + title="Type", + description="Switch between different knob types", + enum_resolver=lambda: knob_types_enum, + conditionalEnum=True + ) + + name: str = Field( + title="Name", + placeholder="Name" + ) + text: str = Field("", title="Value") + color_gui: ColorRGB_uint8 = Field( + (0, 0, 255), + title="RGB Uint8", + ) + boolean: bool = Field(False, title="Value") + number: int = Field(0, title="Value") + decimal_number: float = Field(0.0, title="Value") + vector_2d: Vector2d = Field( + default_factory=Vector2d, + title="Value" + ) + vector_3d: Vector3d = Field( + default_factory=Vector3d, + title="Value" + ) + color: ColorRGBA_float = Field( + (0.0, 0.0, 1.0, 1.0), + title="RGBA Float" + ) + formatable: Formatable = Field( + default_factory=Formatable, + title="Formatable" + ) + expression: str = Field( + "", + title="Expression" + ) diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py new file mode 100644 index 0000000000..0bbae4ee77 --- /dev/null +++ b/server_addon/nuke/server/settings/create_plugins.py @@ -0,0 +1,223 @@ +from pydantic import validator, Field +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names +) +from .common import KnobModel + + +def instance_attributes_enum(): + """Return create write instance attributes.""" + return [ + {"value": "reviewable", "label": "Reviewable"}, + {"value": "farm_rendering", "label": "Farm rendering"}, + {"value": "use_range_limit", "label": "Use range limit"} + ] + + +class PrenodeModel(BaseSettingsModel): + # TODO: missing in host api + # - good for `dependency` + name: str = Field( + title="Node name" + ) + + # TODO: `nodeclass` should be renamed to `nuke_node_class` + nodeclass: str = Field( + "", + title="Node class" + ) + dependent: str = Field( + "", + title="Incoming dependency" + ) + + """# TODO: Changes in host api: + - Need complete rework of knob types in nuke integration. + - We could not support v3 style of settings. + """ + knobs: list[KnobModel] = Field( + title="Knobs", + ) + + @validator("knobs") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class CreateWriteRenderModel(BaseSettingsModel): + temp_rendering_path_template: str = Field( + title="Temporary rendering path template" + ) + default_variants: list[str] = Field( + title="Default variants", + default_factory=list + ) + instance_attributes: list[str] = Field( + default_factory=list, + enum_resolver=instance_attributes_enum, + title="Instance attributes" + ) + + """# TODO: Changes in host api: + - prenodes key was originally dict and now is list + (we could not support v3 style of settings) + """ + prenodes: list[PrenodeModel] = Field( + title="Preceding nodes", + ) + + @validator("prenodes") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class CreateWritePrerenderModel(BaseSettingsModel): + temp_rendering_path_template: str = Field( + title="Temporary rendering path template" + ) + default_variants: list[str] = Field( + title="Default variants", + default_factory=list + ) + instance_attributes: list[str] = Field( + default_factory=list, + enum_resolver=instance_attributes_enum, + title="Instance attributes" + ) + + """# TODO: Changes in host api: + - prenodes key was originally dict and now is list + (we could not support v3 style of settings) + """ + prenodes: list[PrenodeModel] = Field( + title="Preceding nodes", + ) + + @validator("prenodes") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class CreateWriteImageModel(BaseSettingsModel): + temp_rendering_path_template: str = Field( + title="Temporary rendering path template" + ) + default_variants: list[str] = Field( + title="Default variants", + default_factory=list + ) + instance_attributes: list[str] = Field( + default_factory=list, + enum_resolver=instance_attributes_enum, + title="Instance attributes" + ) + + """# TODO: Changes in host api: + - prenodes key was originally dict and now is list + (we could not support v3 style of settings) + """ + prenodes: list[PrenodeModel] = Field( + title="Preceding nodes", + ) + + @validator("prenodes") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class CreatorPluginsSettings(BaseSettingsModel): + CreateWriteRender: CreateWriteRenderModel = Field( + default_factory=CreateWriteRenderModel, + title="Create Write Render" + ) + CreateWritePrerender: CreateWritePrerenderModel = Field( + default_factory=CreateWritePrerenderModel, + title="Create Write Prerender" + ) + CreateWriteImage: CreateWriteImageModel = Field( + default_factory=CreateWriteImageModel, + title="Create Write Image" + ) + + +DEFAULT_CREATE_SETTINGS = { + "CreateWriteRender": { + "temp_rendering_path_template": "{work}/renders/nuke/{product[name]}/{product[name]}.{frame}.{ext}", + "default_variants": [ + "Main", + "Mask" + ], + "instance_attributes": [ + "reviewable", + "farm_rendering" + ], + "prenodes": [ + { + "name": "Reformat01", + "nodeclass": "Reformat", + "dependent": "", + "knobs": [ + { + "type": "text", + "name": "resize", + "text": "none" + }, + { + "type": "boolean", + "name": "black_outside", + "boolean": True + } + ] + } + ] + }, + "CreateWritePrerender": { + "temp_rendering_path_template": "{work}/renders/nuke/{product[name]}/{product[name]}.{frame}.{ext}", + "default_variants": [ + "Key01", + "Bg01", + "Fg01", + "Branch01", + "Part01" + ], + "instance_attributes": [ + "farm_rendering", + "use_range_limit" + ], + "prenodes": [] + }, + "CreateWriteImage": { + "temp_rendering_path_template": "{work}/renders/nuke/{product[name]}/{product[name]}.{ext}", + "default_variants": [ + "StillFrame", + "MPFrame", + "LayoutFrame" + ], + "instance_attributes": [ + "use_range_limit" + ], + "prenodes": [ + { + "name": "FrameHold01", + "nodeclass": "FrameHold", + "dependent": "", + "knobs": [ + { + "type": "expression", + "name": "first_frame", + "expression": "parent.first" + } + ] + } + ] + } +} diff --git a/server_addon/nuke/server/settings/dirmap.py b/server_addon/nuke/server/settings/dirmap.py new file mode 100644 index 0000000000..2da6d7bf60 --- /dev/null +++ b/server_addon/nuke/server/settings/dirmap.py @@ -0,0 +1,47 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class DirmapPathsSubmodel(BaseSettingsModel): + _layout = "compact" + source_path: list[str] = Field( + default_factory=list, + title="Source Paths" + ) + destination_path: list[str] = Field( + default_factory=list, + title="Destination Paths" + ) + + +class DirmapSettings(BaseSettingsModel): + """Nuke color management project settings.""" + _isGroup: bool = True + + enabled: bool = Field(title="enabled") + paths: DirmapPathsSubmodel = Field( + default_factory=DirmapPathsSubmodel, + title="Dirmap Paths" + ) + + +"""# TODO: +nuke is having originally implemented +following data inputs: + +"nuke-dirmap": { + "enabled": false, + "paths": { + "source-path": [], + "destination-path": [] + } +} +""" + +DEFAULT_DIRMAP_SETTINGS = { + "enabled": False, + "paths": { + "source_path": [], + "destination_path": [] + } +} diff --git a/server_addon/nuke/server/settings/filters.py b/server_addon/nuke/server/settings/filters.py new file mode 100644 index 0000000000..7e2702b3b7 --- /dev/null +++ b/server_addon/nuke/server/settings/filters.py @@ -0,0 +1,19 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel, ensure_unique_names + + +class PublishGUIFilterItemModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: bool = Field(True, title="Active") + + +class PublishGUIFiltersModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: list[PublishGUIFilterItemModel] = Field(default_factory=list) + + @validator("value") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value diff --git a/server_addon/nuke/server/settings/general.py b/server_addon/nuke/server/settings/general.py new file mode 100644 index 0000000000..bcbb183952 --- /dev/null +++ b/server_addon/nuke/server/settings/general.py @@ -0,0 +1,42 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class MenuShortcut(BaseSettingsModel): + """Nuke general project settings.""" + + create: str = Field( + title="Create..." + ) + publish: str = Field( + title="Publish..." + ) + load: str = Field( + title="Load..." + ) + manage: str = Field( + title="Manage..." + ) + build_workfile: str = Field( + title="Build Workfile..." + ) + + +class GeneralSettings(BaseSettingsModel): + """Nuke general project settings.""" + + menu: MenuShortcut = Field( + default_factory=MenuShortcut, + title="Menu Shortcuts", + ) + + +DEFAULT_GENERAL_SETTINGS = { + "menu": { + "create": "ctrl+alt+c", + "publish": "ctrl+alt+p", + "load": "ctrl+alt+l", + "manage": "ctrl+alt+m", + "build_workfile": "ctrl+alt+b" + } +} diff --git a/server_addon/nuke/server/settings/gizmo.py b/server_addon/nuke/server/settings/gizmo.py new file mode 100644 index 0000000000..4cdd614da8 --- /dev/null +++ b/server_addon/nuke/server/settings/gizmo.py @@ -0,0 +1,79 @@ +from pydantic import Field +from ayon_server.settings import ( + BaseSettingsModel, + MultiplatformPathModel, + MultiplatformPathListModel, +) + + +class SubGizmoItem(BaseSettingsModel): + title: str = Field( + title="Label" + ) + sourcetype: str = Field( + title="Type of usage" + ) + command: str = Field( + title="Python command" + ) + icon: str = Field( + title="Icon Path" + ) + shortcut: str = Field( + title="Hotkey" + ) + + +class GizmoDefinitionItem(BaseSettingsModel): + gizmo_toolbar_path: str = Field( + title="Gizmo Menu" + ) + sub_gizmo_list: list[SubGizmoItem] = Field( + default_factory=list, title="Sub Gizmo List") + + +class GizmoItem(BaseSettingsModel): + """Nuke gizmo item """ + + toolbar_menu_name: str = Field( + title="Toolbar Menu Name" + ) + gizmo_source_dir: MultiplatformPathListModel = Field( + default_factory=MultiplatformPathListModel, + title="Gizmo Directory Path" + ) + toolbar_icon_path: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Toolbar Icon Path" + ) + gizmo_definition: list[GizmoDefinitionItem] = Field( + default_factory=list, title="Gizmo Definition") + + +DEFAULT_GIZMO_ITEM = { + "toolbar_menu_name": "OpenPype Gizmo", + "gizmo_source_dir": { + "windows": [], + "darwin": [], + "linux": [] + }, + "toolbar_icon_path": { + "windows": "", + "darwin": "", + "linux": "" + }, + "gizmo_definition": [ + { + "gizmo_toolbar_path": "/path/to/menu", + "sub_gizmo_list": [ + { + "sourcetype": "python", + "title": "Gizmo Note", + "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')", + "icon": "", + "shortcut": "" + } + ] + } + ] +} diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py new file mode 100644 index 0000000000..b43017ef8b --- /dev/null +++ b/server_addon/nuke/server/settings/imageio.py @@ -0,0 +1,410 @@ +from typing import Literal +from pydantic import validator, Field +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names, +) + +from .common import KnobModel + + +class NodesModel(BaseSettingsModel): + """# TODO: This needs to be somehow labeled in settings panel + or at least it could show gist of configuration + """ + _layout = "expanded" + plugins: list[str] = Field( + title="Used in plugins" + ) + # TODO: rename `nukeNodeClass` to `nuke_node_class` + nukeNodeClass: str = Field( + title="Nuke Node Class", + ) + + """ # TODO: Need complete rework of knob types + in nuke integration. We could not support v3 style of settings. + """ + knobs: list[KnobModel] = Field( + title="Knobs", + ) + + @validator("knobs") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class NodesSetting(BaseSettingsModel): + # TODO: rename `requiredNodes` to `required_nodes` + requiredNodes: list[NodesModel] = Field( + title="Plugin required", + default_factory=list + ) + # TODO: rename `overrideNodes` to `override_nodes` + overrideNodes: list[NodesModel] = Field( + title="Plugin's node overrides", + default_factory=list + ) + + +def ocio_configs_switcher_enum(): + return [ + {"value": "nuke-default", "label": "nuke-default"}, + {"value": "spi-vfx", "label": "spi-vfx"}, + {"value": "spi-anim", "label": "spi-anim"}, + {"value": "aces_0.1.1", "label": "aces_0.1.1"}, + {"value": "aces_0.7.1", "label": "aces_0.7.1"}, + {"value": "aces_1.0.1", "label": "aces_1.0.1"}, + {"value": "aces_1.0.3", "label": "aces_1.0.3"}, + {"value": "aces_1.1", "label": "aces_1.1"}, + {"value": "aces_1.2", "label": "aces_1.2"}, + {"value": "aces_1.3", "label": "aces_1.3"}, + {"value": "custom", "label": "custom"} + ] + + +class WorkfileColorspaceSettings(BaseSettingsModel): + """Nuke workfile colorspace preset. """ + """# TODO: enhance settings with host api: + we need to add mapping to resolve properly keys. + Nuke is excpecting camel case key names, + but for better code consistency we need to + be using snake_case: + + color_management = colorManagement + ocio_config = OCIO_config + working_space_name = workingSpaceLUT + monitor_name = monitorLut + monitor_out_name = monitorOutLut + int_8_name = int8Lut + int_16_name = int16Lut + log_name = logLut + float_name = floatLut + """ + + colorManagement: Literal["Nuke", "OCIO"] = Field( + title="Color Management" + ) + + OCIO_config: str = Field( + title="OpenColorIO Config", + description="Switch between OCIO configs", + enum_resolver=ocio_configs_switcher_enum, + conditionalEnum=True + ) + + workingSpaceLUT: str = Field( + title="Working Space" + ) + monitorLut: str = Field( + title="Monitor" + ) + int8Lut: str = Field( + title="8-bit files" + ) + int16Lut: str = Field( + title="16-bit files" + ) + logLut: str = Field( + title="Log files" + ) + floatLut: str = Field( + title="Float files" + ) + + +class ReadColorspaceRulesItems(BaseSettingsModel): + _layout = "expanded" + + regex: str = Field("", title="Regex expression") + colorspace: str = Field("", title="Colorspace") + + +class RegexInputsModel(BaseSettingsModel): + inputs: list[ReadColorspaceRulesItems] = Field( + default_factory=list, + title="Inputs" + ) + + +class ViewProcessModel(BaseSettingsModel): + viewerProcess: str = Field( + title="Viewer Process Name" + ) + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ImageIOSettings(BaseSettingsModel): + """Nuke color management project settings. """ + _isGroup: bool = True + + """# TODO: enhance settings with host api: + to restruture settings for simplification. + + now: nuke/imageio/viewer/viewerProcess + future: nuke/imageio/viewer + """ + activate_host_color_management: bool = Field( + True, title="Enable Color Management") + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) + viewer: ViewProcessModel = Field( + default_factory=ViewProcessModel, + title="Viewer", + description="""Viewer profile is used during + Creation of new viewer node at knob viewerProcess""" + ) + + """# TODO: enhance settings with host api: + to restruture settings for simplification. + + now: nuke/imageio/baking/viewerProcess + future: nuke/imageio/baking + """ + baking: ViewProcessModel = Field( + default_factory=ViewProcessModel, + title="Baking", + description="""Baking profile is used during + publishing baked colorspace data at knob viewerProcess""" + ) + + workfile: WorkfileColorspaceSettings = Field( + default_factory=WorkfileColorspaceSettings, + title="Workfile" + ) + + nodes: NodesSetting = Field( + default_factory=NodesSetting, + title="Nodes" + ) + """# TODO: enhance settings with host api: + - old settings are using `regexInputs` key but we + need to rename to `regex_inputs` + - no need for `inputs` middle part. It can stay + directly on `regex_inputs` + """ + regexInputs: RegexInputsModel = Field( + default_factory=RegexInputsModel, + title="Assign colorspace to read nodes via rules" + ) + + +DEFAULT_IMAGEIO_SETTINGS = { + "viewer": { + "viewerProcess": "sRGB" + }, + "baking": { + "viewerProcess": "rec709" + }, + "workfile": { + "colorManagement": "Nuke", + "OCIO_config": "nuke-default", + "workingSpaceLUT": "linear", + "monitorLut": "sRGB", + "int8Lut": "sRGB", + "int16Lut": "sRGB", + "logLut": "Cineon", + "floatLut": "linear" + }, + "nodes": { + "requiredNodes": [ + { + "plugins": [ + "CreateWriteRender" + ], + "nukeNodeClass": "Write", + "knobs": [ + { + "type": "text", + "name": "file_type", + "text": "exr" + }, + { + "type": "text", + "name": "datatype", + "text": "16 bit half" + }, + { + "type": "text", + "name": "compression", + "text": "Zip (1 scanline)" + }, + { + "type": "boolean", + "name": "autocrop", + "boolean": True + }, + { + "type": "color_gui", + "name": "tile_color", + "color_gui": [ + 186, + 35, + 35 + ] + }, + { + "type": "text", + "name": "channels", + "text": "rgb" + }, + { + "type": "text", + "name": "colorspace", + "text": "linear" + }, + { + "type": "boolean", + "name": "create_directories", + "boolean": True + } + ] + }, + { + "plugins": [ + "CreateWritePrerender" + ], + "nukeNodeClass": "Write", + "knobs": [ + { + "type": "text", + "name": "file_type", + "text": "exr" + }, + { + "type": "text", + "name": "datatype", + "text": "16 bit half" + }, + { + "type": "text", + "name": "compression", + "text": "Zip (1 scanline)" + }, + { + "type": "boolean", + "name": "autocrop", + "boolean": True + }, + { + "type": "color_gui", + "name": "tile_color", + "color_gui": [ + 171, + 171, + 10 + ] + }, + { + "type": "text", + "name": "channels", + "text": "rgb" + }, + { + "type": "text", + "name": "colorspace", + "text": "linear" + }, + { + "type": "boolean", + "name": "create_directories", + "boolean": True + } + ] + }, + { + "plugins": [ + "CreateWriteImage" + ], + "nukeNodeClass": "Write", + "knobs": [ + { + "type": "text", + "name": "file_type", + "text": "tiff" + }, + { + "type": "text", + "name": "datatype", + "text": "16 bit" + }, + { + "type": "text", + "name": "compression", + "text": "Deflate" + }, + { + "type": "color_gui", + "name": "tile_color", + "color_gui": [ + 56, + 162, + 7 + ] + }, + { + "type": "text", + "name": "channels", + "text": "rgb" + }, + { + "type": "text", + "name": "colorspace", + "text": "sRGB" + }, + { + "type": "boolean", + "name": "create_directories", + "boolean": True + } + ] + } + ], + "overrideNodes": [] + }, + "regexInputs": { + "inputs": [ + { + "regex": "(beauty).*(?=.exr)", + "colorspace": "linear" + } + ] + } +} diff --git a/server_addon/nuke/server/settings/loader_plugins.py b/server_addon/nuke/server/settings/loader_plugins.py new file mode 100644 index 0000000000..6db381bffb --- /dev/null +++ b/server_addon/nuke/server/settings/loader_plugins.py @@ -0,0 +1,80 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class LoadImageModel(BaseSettingsModel): + enabled: bool = Field( + title="Enabled" + ) + """# TODO: v3 api used `_representation` + New api is hiding it so it had to be renamed + to `representations_include` + """ + representations_include: list[str] = Field( + default_factory=list, + title="Include representations" + ) + + node_name_template: str = Field( + title="Read node name template" + ) + + +class LoadClipOptionsModel(BaseSettingsModel): + start_at_workfile: bool = Field( + title="Start at workfile's start frame" + ) + add_retime: bool = Field( + title="Add retime" + ) + + +class LoadClipModel(BaseSettingsModel): + enabled: bool = Field( + title="Enabled" + ) + """# TODO: v3 api used `_representation` + New api is hiding it so it had to be renamed + to `representations_include` + """ + representations_include: list[str] = Field( + default_factory=list, + title="Include representations" + ) + + node_name_template: str = Field( + title="Read node name template" + ) + options_defaults: LoadClipOptionsModel = Field( + default_factory=LoadClipOptionsModel, + title="Loader option defaults" + ) + + +class LoaderPuginsModel(BaseSettingsModel): + LoadImage: LoadImageModel = Field( + default_factory=LoadImageModel, + title="Load Image" + ) + LoadClip: LoadClipModel = Field( + default_factory=LoadClipModel, + title="Load Clip" + ) + + +DEFAULT_LOADER_PLUGINS_SETTINGS = { + "LoadImage": { + "enabled": True, + "representations_include": [], + "node_name_template": "{class_name}_{ext}" + }, + "LoadClip": { + "enabled": True, + "representations_include": [], + "node_name_template": "{class_name}_{ext}", + "options_defaults": { + "start_at_workfile": True, + "add_retime": True + } + } +} diff --git a/server_addon/nuke/server/settings/main.py b/server_addon/nuke/server/settings/main.py new file mode 100644 index 0000000000..4687d48ac9 --- /dev/null +++ b/server_addon/nuke/server/settings/main.py @@ -0,0 +1,128 @@ +from pydantic import validator, Field + +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names +) + +from .general import ( + GeneralSettings, + DEFAULT_GENERAL_SETTINGS +) +from .imageio import ( + ImageIOSettings, + DEFAULT_IMAGEIO_SETTINGS +) +from .dirmap import ( + DirmapSettings, + DEFAULT_DIRMAP_SETTINGS +) +from .scriptsmenu import ( + ScriptsmenuSettings, + DEFAULT_SCRIPTSMENU_SETTINGS +) +from .gizmo import ( + GizmoItem, + DEFAULT_GIZMO_ITEM +) +from .create_plugins import ( + CreatorPluginsSettings, + DEFAULT_CREATE_SETTINGS +) +from .publish_plugins import ( + PublishPuginsModel, + DEFAULT_PUBLISH_PLUGIN_SETTINGS +) +from .loader_plugins import ( + LoaderPuginsModel, + DEFAULT_LOADER_PLUGINS_SETTINGS +) +from .workfile_builder import ( + WorkfileBuilderModel, + DEFAULT_WORKFILE_BUILDER_SETTINGS +) +from .templated_workfile_build import ( + TemplatedWorkfileBuildModel +) +from .filters import PublishGUIFilterItemModel + + +class NukeSettings(BaseSettingsModel): + """Nuke addon settings.""" + + general: GeneralSettings = Field( + default_factory=GeneralSettings, + title="General", + ) + + imageio: ImageIOSettings = Field( + default_factory=ImageIOSettings, + title="Color Management (imageio)", + ) + """# TODO: fix host api: + - rename `nuke-dirmap` to `dirmap` was inevitable + """ + dirmap: DirmapSettings = Field( + default_factory=DirmapSettings, + title="Nuke Directory Mapping", + ) + + scriptsmenu: ScriptsmenuSettings = Field( + default_factory=ScriptsmenuSettings, + title="Scripts Menu Definition", + ) + + gizmo: list[GizmoItem] = Field( + default_factory=list, title="Gizmo Menu") + + create: CreatorPluginsSettings = Field( + default_factory=CreatorPluginsSettings, + title="Creator Plugins", + ) + + publish: PublishPuginsModel = Field( + default_factory=PublishPuginsModel, + title="Publish Plugins", + ) + + load: LoaderPuginsModel = Field( + default_factory=LoaderPuginsModel, + title="Loader Plugins", + ) + + workfile_builder: WorkfileBuilderModel = Field( + default_factory=WorkfileBuilderModel, + title="Workfile Builder", + ) + + templated_workfile_build: TemplatedWorkfileBuildModel = Field( + title="Templated Workfile Build", + default_factory=TemplatedWorkfileBuildModel + ) + + filters: list[PublishGUIFilterItemModel] = Field( + default_factory=list + ) + + @validator("filters") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +DEFAULT_VALUES = { + "general": DEFAULT_GENERAL_SETTINGS, + "imageio": DEFAULT_IMAGEIO_SETTINGS, + "dirmap": DEFAULT_DIRMAP_SETTINGS, + "scriptsmenu": DEFAULT_SCRIPTSMENU_SETTINGS, + "gizmo": [DEFAULT_GIZMO_ITEM], + "create": DEFAULT_CREATE_SETTINGS, + "publish": DEFAULT_PUBLISH_PLUGIN_SETTINGS, + "load": DEFAULT_LOADER_PLUGINS_SETTINGS, + "workfile_builder": DEFAULT_WORKFILE_BUILDER_SETTINGS, + "templated_workfile_build": { + "profiles": [] + }, + "filters": [] +} diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py new file mode 100644 index 0000000000..f057fd629d --- /dev/null +++ b/server_addon/nuke/server/settings/publish_plugins.py @@ -0,0 +1,536 @@ +from pydantic import validator, Field +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names, + task_types_enum +) +from .common import KnobModel, validate_json_dict + + +def nuke_render_publish_types_enum(): + """Return all nuke render families available in creators.""" + return [ + {"value": "render", "label": "Render"}, + {"value": "prerender", "label": "Prerender"}, + {"value": "image", "label": "Image"} + ] + + +def nuke_product_types_enum(): + """Return all nuke families available in creators.""" + return [ + {"value": "nukenodes", "label": "Nukenodes"}, + {"value": "model", "label": "Model"}, + {"value": "camera", "label": "Camera"}, + {"value": "gizmo", "label": "Gizmo"}, + {"value": "source", "label": "Source"} + ] + nuke_render_publish_types_enum() + + +class NodeModel(BaseSettingsModel): + # TODO: missing in host api + name: str = Field( + title="Node name" + ) + # TODO: `nodeclass` rename to `nuke_node_class` + nodeclass: str = Field( + "", + title="Node class" + ) + dependent: str = Field( + "", + title="Incoming dependency" + ) + """# TODO: Changes in host api: + - Need complete rework of knob types in nuke integration. + - We could not support v3 style of settings. + """ + knobs: list[KnobModel] = Field( + title="Knobs", + ) + + @validator("knobs") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class ThumbnailRepositionNodeModel(BaseSettingsModel): + node_class: str = Field(title="Node class") + knobs: list[KnobModel] = Field(title="Knobs", default_factory=list) + + @validator("knobs") + def ensure_unique_names(cls, value): + """Ensure name fields within the lists have unique names.""" + ensure_unique_names(value) + return value + + +class CollectInstanceDataModel(BaseSettingsModel): + sync_workfile_version_on_product_types: list[str] = Field( + default_factory=list, + enum_resolver=nuke_product_types_enum, + title="Sync workfile versions for familes" + ) + + +class OptionalPluginModel(BaseSettingsModel): + enabled: bool = Field(True) + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + + +class ValidateKnobsModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + knobs: str = Field( + "{}", + title="Knobs", + widget="textarea", + ) + + @validator("knobs") + def validate_json(cls, value): + return validate_json_dict(value) + + +class ExtractThumbnailModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + use_rendered: bool = Field(title="Use rendered images") + bake_viewer_process: bool = Field(title="Bake view process") + bake_viewer_input_process: bool = Field(title="Bake viewer input process") + """# TODO: needs to rewrite from v3 to ayon + - `nodes` in v3 was dict but now `prenodes` is list of dict + - also later `nodes` should be `prenodes` + """ + + nodes: list[NodeModel] = Field( + title="Nodes (deprecated)" + ) + reposition_nodes: list[ThumbnailRepositionNodeModel] = Field( + title="Reposition nodes", + default_factory=list + ) + + +class ExtractReviewDataModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + + +class ExtractReviewDataLutModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + + +class BakingStreamFilterModel(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + product_types: list[str] = Field( + default_factory=list, + enum_resolver=nuke_render_publish_types_enum, + title="Sync workfile versions for familes" + ) + product_names: list[str] = Field( + default_factory=list, title="Product names") + + +class ReformatNodesRepositionNodes(BaseSettingsModel): + node_class: str = Field(title="Node class") + knobs: list[KnobModel] = Field( + default_factory=list, + title="Node knobs") + + +class ReformatNodesConfigModel(BaseSettingsModel): + """Only reposition nodes supported. + + You can add multiple reformat nodes and set their knobs. + Order of reformat nodes is important. First reformat node will + be applied first and last reformat node will be applied last. + """ + enabled: bool = Field(False) + reposition_nodes: list[ReformatNodesRepositionNodes] = Field( + default_factory=list, + title="Reposition knobs" + ) + + +class BakingStreamModel(BaseSettingsModel): + name: str = Field(title="Output name") + filter: BakingStreamFilterModel = Field( + title="Filter", default_factory=BakingStreamFilterModel) + read_raw: bool = Field(title="Read raw switch") + viewer_process_override: str = Field(title="Viewer process override") + bake_viewer_process: bool = Field(title="Bake view process") + bake_viewer_input_process: bool = Field(title="Bake viewer input process") + reformat_node_add: bool = Field(title="Add reformat node") + reformat_node_config: list[KnobModel] = Field( + default_factory=list, + title="Reformat node properties") + reformat_nodes_config: ReformatNodesConfigModel = Field( + default_factory=ReformatNodesConfigModel, + title="Reformat Nodes") + extension: str = Field(title="File extension") + add_custom_tags: list[str] = Field( + title="Custom tags", default_factory=list) + + +class ExtractReviewDataMovModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + viewer_lut_raw: bool = Field(title="Viewer lut raw") + outputs: list[BakingStreamModel] = Field( + title="Baking streams" + ) + + +class FSubmissionNoteModel(BaseSettingsModel): + enabled: bool = Field(title="enabled") + template: str = Field(title="Template") + + +class FSubmistingForModel(BaseSettingsModel): + enabled: bool = Field(title="enabled") + template: str = Field(title="Template") + + +class FVFXScopeOfWorkModel(BaseSettingsModel): + enabled: bool = Field(title="enabled") + template: str = Field(title="Template") + + +class ExctractSlateFrameParamModel(BaseSettingsModel): + f_submission_note: FSubmissionNoteModel = Field( + title="f_submission_note", + default_factory=FSubmissionNoteModel + ) + f_submitting_for: FSubmistingForModel = Field( + title="f_submitting_for", + default_factory=FSubmistingForModel + ) + f_vfx_scope_of_work: FVFXScopeOfWorkModel = Field( + title="f_vfx_scope_of_work", + default_factory=FVFXScopeOfWorkModel + ) + + +class ExtractSlateFrameModel(BaseSettingsModel): + viewer_lut_raw: bool = Field(title="Viewer lut raw") + """# TODO: v3 api different model: + - not possible to replicate v3 model: + {"name": [bool, str]} + - not it is: + {"name": {"enabled": bool, "template": str}} + """ + key_value_mapping: ExctractSlateFrameParamModel = Field( + title="Key value mapping", + default_factory=ExctractSlateFrameParamModel + ) + + +class IncrementScriptVersionModel(BaseSettingsModel): + enabled: bool = Field(title="Enabled") + optional: bool = Field(title="Optional") + active: bool = Field(title="Active") + + +class PublishPuginsModel(BaseSettingsModel): + CollectInstanceData: CollectInstanceDataModel = Field( + title="Collect Instance Version", + default_factory=CollectInstanceDataModel, + section="Collectors" + ) + ValidateCorrectAssetName: OptionalPluginModel = Field( + title="Validate Correct Folder Name", + default_factory=OptionalPluginModel, + section="Validators" + ) + ValidateContainers: OptionalPluginModel = Field( + title="Validate Containers", + default_factory=OptionalPluginModel + ) + ValidateKnobs: ValidateKnobsModel = Field( + title="Validate Knobs", + default_factory=ValidateKnobsModel + ) + ValidateOutputResolution: OptionalPluginModel = Field( + title="Validate Output Resolution", + default_factory=OptionalPluginModel + ) + ValidateGizmo: OptionalPluginModel = Field( + title="Validate Gizmo", + default_factory=OptionalPluginModel + ) + ValidateBackdrop: OptionalPluginModel = Field( + title="Validate Backdrop", + default_factory=OptionalPluginModel + ) + ValidateScript: OptionalPluginModel = Field( + title="Validate Script", + default_factory=OptionalPluginModel + ) + ExtractThumbnail: ExtractThumbnailModel = Field( + title="Extract Thumbnail", + default_factory=ExtractThumbnailModel, + section="Extractors" + ) + ExtractReviewData: ExtractReviewDataModel = Field( + title="Extract Review Data", + default_factory=ExtractReviewDataModel + ) + ExtractReviewDataLut: ExtractReviewDataLutModel = Field( + title="Extract Review Data Lut", + default_factory=ExtractReviewDataLutModel + ) + ExtractReviewDataMov: ExtractReviewDataMovModel = Field( + title="Extract Review Data Mov", + default_factory=ExtractReviewDataMovModel + ) + ExtractSlateFrame: ExtractSlateFrameModel = Field( + title="Extract Slate Frame", + default_factory=ExtractSlateFrameModel + ) + # TODO: plugin should be renamed - `workfile` not `script` + IncrementScriptVersion: IncrementScriptVersionModel = Field( + title="Increment Workfile Version", + default_factory=IncrementScriptVersionModel, + section="Integrators" + ) + + +DEFAULT_PUBLISH_PLUGIN_SETTINGS = { + "CollectInstanceData": { + "sync_workfile_version_on_product_types": [ + "nukenodes", + "camera", + "gizmo", + "source", + "render", + "write" + ] + }, + "ValidateCorrectAssetName": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateContainers": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateKnobs": { + "enabled": False, + "knobs": "\n".join([ + '{', + ' "render": {', + ' "review": true', + ' }', + '}' + ]) + }, + "ValidateOutputResolution": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateGizmo": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateBackdrop": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateScript": { + "enabled": True, + "optional": True, + "active": True + }, + "ExtractThumbnail": { + "enabled": True, + "use_rendered": True, + "bake_viewer_process": True, + "bake_viewer_input_process": True, + "nodes": [ + { + "name": "Reformat01", + "nodeclass": "Reformat", + "dependency": "", + "knobs": [ + { + "type": "text", + "name": "type", + "text": "to format" + }, + { + "type": "text", + "name": "format", + "text": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "text": "Lanczos6" + }, + { + "type": "boolean", + "name": "black_outside", + "boolean": True + }, + { + "type": "boolean", + "name": "pbb", + "boolean": False + } + ] + } + ], + "reposition_nodes": [ + { + "node_class": "Reformat", + "knobs": [ + { + "type": "text", + "name": "type", + "text": "to format" + }, + { + "type": "text", + "name": "format", + "text": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "text": "Lanczos6" + }, + { + "type": "bool", + "name": "black_outside", + "boolean": True + }, + { + "type": "bool", + "name": "pbb", + "boolean": False + } + ] + } + ] + }, + "ExtractReviewData": { + "enabled": False + }, + "ExtractReviewDataLut": { + "enabled": False + }, + "ExtractReviewDataMov": { + "enabled": True, + "viewer_lut_raw": False, + "outputs": [ + { + "name": "baking", + "filter": { + "task_types": [], + "product_types": [], + "product_names": [] + }, + "read_raw": False, + "viewer_process_override": "", + "bake_viewer_process": True, + "bake_viewer_input_process": True, + "reformat_node_add": False, + "reformat_node_config": [ + { + "type": "text", + "name": "type", + "text": "to format" + }, + { + "type": "text", + "name": "format", + "text": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "text": "Lanczos6" + }, + { + "type": "boolean", + "name": "black_outside", + "boolean": True + }, + { + "type": "boolean", + "name": "pbb", + "boolean": False + } + ], + "reformat_nodes_config": { + "enabled": False, + "reposition_nodes": [ + { + "node_class": "Reformat", + "knobs": [ + { + "type": "text", + "name": "type", + "text": "to format" + }, + { + "type": "text", + "name": "format", + "text": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "text": "Lanczos6" + }, + { + "type": "bool", + "name": "black_outside", + "boolean": True + }, + { + "type": "bool", + "name": "pbb", + "boolean": False + } + ] + } + ] + }, + "extension": "mov", + "add_custom_tags": [] + } + ] + }, + "ExtractSlateFrame": { + "viewer_lut_raw": False, + "key_value_mapping": { + "f_submission_note": { + "enabled": True, + "template": "{comment}" + }, + "f_submitting_for": { + "enabled": True, + "template": "{intent[value]}" + }, + "f_vfx_scope_of_work": { + "enabled": False, + "template": "" + } + } + }, + "IncrementScriptVersion": { + "enabled": True, + "optional": True, + "active": True + } +} diff --git a/server_addon/nuke/server/settings/scriptsmenu.py b/server_addon/nuke/server/settings/scriptsmenu.py new file mode 100644 index 0000000000..9d1c32ebac --- /dev/null +++ b/server_addon/nuke/server/settings/scriptsmenu.py @@ -0,0 +1,54 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class ScriptsmenuSubmodel(BaseSettingsModel): + """Item Definition""" + _isGroup = True + + type: str = Field(title="Type") + command: str = Field(title="Command") + sourcetype: str = Field(title="Source Type") + title: str = Field(title="Title") + tooltip: str = Field(title="Tooltip") + + +class ScriptsmenuSettings(BaseSettingsModel): + """Nuke script menu project settings.""" + _isGroup = True + + # TODO: in api rename key `name` to `menu_name` + name: str = Field(title="Menu Name") + definition: list[ScriptsmenuSubmodel] = Field( + default_factory=list, + title="Definition", + description="Scriptmenu Items Definition" + ) + + +DEFAULT_SCRIPTSMENU_SETTINGS = { + "name": "OpenPype Tools", + "definition": [ + { + "type": "action", + "sourcetype": "python", + "title": "OpenPype Docs", + "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')", + "tooltip": "Open the OpenPype Nuke user doc page" + }, + { + "type": "action", + "sourcetype": "python", + "title": "Set Frame Start (Read Node)", + "command": "from openpype.hosts.nuke.startup.frame_setting_for_read_nodes import main;main();", + "tooltip": "Set frame start for read node(s)" + }, + { + "type": "action", + "sourcetype": "python", + "title": "Set non publish output for Write Node", + "command": "from openpype.hosts.nuke.startup.custom_write_node import main;main();", + "tooltip": "Open the OpenPype Nuke user doc page" + } + ] +} diff --git a/server_addon/nuke/server/settings/templated_workfile_build.py b/server_addon/nuke/server/settings/templated_workfile_build.py new file mode 100644 index 0000000000..e0245c8d06 --- /dev/null +++ b/server_addon/nuke/server/settings/templated_workfile_build.py @@ -0,0 +1,33 @@ +from pydantic import Field +from ayon_server.settings import ( + BaseSettingsModel, + task_types_enum, +) + + +class TemplatedWorkfileProfileModel(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = Field( + default_factory=list, + title="Task names" + ) + path: str = Field( + title="Path to template" + ) + keep_placeholder: bool = Field( + False, + title="Keep placeholders") + create_first_version: bool = Field( + True, + title="Create first version" + ) + + +class TemplatedWorkfileBuildModel(BaseSettingsModel): + profiles: list[TemplatedWorkfileProfileModel] = Field( + default_factory=list + ) diff --git a/server_addon/nuke/server/settings/workfile_builder.py b/server_addon/nuke/server/settings/workfile_builder.py new file mode 100644 index 0000000000..ee67c7c16a --- /dev/null +++ b/server_addon/nuke/server/settings/workfile_builder.py @@ -0,0 +1,72 @@ +from pydantic import Field +from ayon_server.settings import ( + BaseSettingsModel, + task_types_enum, + MultiplatformPathModel, +) + + +class CustomTemplateModel(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + path: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel, + title="Gizmo Directory Path" + ) + + +class BuilderProfileItemModel(BaseSettingsModel): + product_name_filters: list[str] = Field( + default_factory=list, + title="Product name" + ) + product_types: list[str] = Field( + default_factory=list, + title="Product types" + ) + repre_names: list[str] = Field( + default_factory=list, + title="Representations" + ) + loaders: list[str] = Field( + default_factory=list, + title="Loader plugins" + ) + + +class BuilderProfileModel(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + tasks: list[str] = Field( + default_factory=list, + title="Task names" + ) + current_context: list[BuilderProfileItemModel] = Field( + title="Current context") + linked_assets: list[BuilderProfileItemModel] = Field( + title="Linked assets/shots") + + +class WorkfileBuilderModel(BaseSettingsModel): + create_first_version: bool = Field( + title="Create first workfile") + custom_templates: list[CustomTemplateModel] = Field( + title="Custom templates") + builder_on_start: bool = Field( + title="Run Builder at first workfile") + profiles: list[BuilderProfileModel] = Field( + title="Builder profiles") + + +DEFAULT_WORKFILE_BUILDER_SETTINGS = { + "create_first_version": False, + "custom_templates": [], + "builder_on_start": False, + "profiles": [] +} diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/nuke/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/client/pyproject.toml b/server_addon/openpype/client/pyproject.toml similarity index 100% rename from server_addon/client/pyproject.toml rename to server_addon/openpype/client/pyproject.toml diff --git a/server_addon/server/__init__.py b/server_addon/openpype/server/__init__.py similarity index 100% rename from server_addon/server/__init__.py rename to server_addon/openpype/server/__init__.py diff --git a/server_addon/photoshop/LICENSE b/server_addon/photoshop/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/server_addon/photoshop/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/server_addon/photoshop/README.md b/server_addon/photoshop/README.md new file mode 100644 index 0000000000..2d1e1c745c --- /dev/null +++ b/server_addon/photoshop/README.md @@ -0,0 +1,4 @@ +Photoshp Addon +=============== + +Integration with Adobe Photoshop. diff --git a/server_addon/photoshop/server/__init__.py b/server_addon/photoshop/server/__init__.py new file mode 100644 index 0000000000..e7ac218b5a --- /dev/null +++ b/server_addon/photoshop/server/__init__.py @@ -0,0 +1,15 @@ +from ayon_server.addons import BaseServerAddon + +from .settings import PhotoshopSettings, DEFAULT_PHOTOSHOP_SETTING +from .version import __version__ + + +class Photoshop(BaseServerAddon): + name = "photoshop" + version = __version__ + + settings_model = PhotoshopSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_PHOTOSHOP_SETTING) diff --git a/server_addon/photoshop/server/settings/__init__.py b/server_addon/photoshop/server/settings/__init__.py new file mode 100644 index 0000000000..9ae5764362 --- /dev/null +++ b/server_addon/photoshop/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + PhotoshopSettings, + DEFAULT_PHOTOSHOP_SETTING, +) + + +__all__ = ( + "PhotoshopSettings", + "DEFAULT_PHOTOSHOP_SETTING", +) diff --git a/server_addon/photoshop/server/settings/creator_plugins.py b/server_addon/photoshop/server/settings/creator_plugins.py new file mode 100644 index 0000000000..2fe63a7e3a --- /dev/null +++ b/server_addon/photoshop/server/settings/creator_plugins.py @@ -0,0 +1,79 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class CreateImagePluginModel(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + active_on_create: bool = Field(True, title="Active by default") + mark_for_review: bool = Field(False, title="Review by default") + default_variants: list[str] = Field( + default_factory=list, + title="Default Variants" + ) + + +class AutoImageCreatorPluginModel(BaseSettingsModel): + enabled: bool = Field(False, title="Enabled") + active_on_create: bool = Field(True, title="Active by default") + mark_for_review: bool = Field(False, title="Review by default") + default_variant: str = Field("", title="Default Variants") + + +class CreateReviewPlugin(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + active_on_create: bool = Field(True, title="Active by default") + default_variant: str = Field("", title="Default Variants") + + +class CreateWorkfilelugin(BaseSettingsModel): + enabled: bool = Field(True, title="Enabled") + active_on_create: bool = Field(True, title="Active by default") + default_variant: str = Field("", title="Default Variants") + + +class PhotoshopCreatorPlugins(BaseSettingsModel): + ImageCreator: CreateImagePluginModel = Field( + title="Create Image", + default_factory=CreateImagePluginModel, + ) + AutoImageCreator: AutoImageCreatorPluginModel = Field( + title="Create Flatten Image", + default_factory=AutoImageCreatorPluginModel, + ) + ReviewCreator: CreateReviewPlugin = Field( + title="Create Review", + default_factory=CreateReviewPlugin, + ) + WorkfileCreator: CreateWorkfilelugin = Field( + title="Create Workfile", + default_factory=CreateWorkfilelugin, + ) + + +DEFAULT_CREATE_SETTINGS = { + "ImageCreator": { + "enabled": True, + "active_on_create": True, + "mark_for_review": False, + "default_variants": [ + "Main" + ] + }, + "AutoImageCreator": { + "enabled": False, + "active_on_create": True, + "mark_for_review": False, + "default_variant": "" + }, + "ReviewCreator": { + "enabled": True, + "active_on_create": True, + "default_variant": "" + }, + "WorkfileCreator": { + "enabled": True, + "active_on_create": True, + "default_variant": "Main" + } +} diff --git a/server_addon/photoshop/server/settings/imageio.py b/server_addon/photoshop/server/settings/imageio.py new file mode 100644 index 0000000000..56b7f2fa32 --- /dev/null +++ b/server_addon/photoshop/server/settings/imageio.py @@ -0,0 +1,64 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ImageIORemappingRulesModel(BaseSettingsModel): + host_native_name: str = Field( + title="Application native colorspace name" + ) + ocio_name: str = Field(title="OCIO colorspace name") + + +class ImageIORemappingModel(BaseSettingsModel): + rules: list[ImageIORemappingRulesModel] = Field( + default_factory=list) + + +class PhotoshopImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + remapping: ImageIORemappingModel = Field( + title="Remapping colorspace names", + default_factory=ImageIORemappingModel + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/photoshop/server/settings/main.py b/server_addon/photoshop/server/settings/main.py new file mode 100644 index 0000000000..ae7705b3db --- /dev/null +++ b/server_addon/photoshop/server/settings/main.py @@ -0,0 +1,41 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + +from .imageio import PhotoshopImageIOModel +from .creator_plugins import PhotoshopCreatorPlugins, DEFAULT_CREATE_SETTINGS +from .publish_plugins import PhotoshopPublishPlugins, DEFAULT_PUBLISH_SETTINGS +from .workfile_builder import WorkfileBuilderPlugin + + +class PhotoshopSettings(BaseSettingsModel): + """Photoshop Project Settings.""" + + imageio: PhotoshopImageIOModel = Field( + default_factory=PhotoshopImageIOModel, + title="OCIO config" + ) + + create: PhotoshopCreatorPlugins = Field( + default_factory=PhotoshopCreatorPlugins, + title="Creator plugins" + ) + + publish: PhotoshopPublishPlugins = Field( + default_factory=PhotoshopPublishPlugins, + title="Publish plugins" + ) + + workfile_builder: WorkfileBuilderPlugin = Field( + default_factory=WorkfileBuilderPlugin, + title="Workfile Builder" + ) + + +DEFAULT_PHOTOSHOP_SETTING = { + "create": DEFAULT_CREATE_SETTINGS, + "publish": DEFAULT_PUBLISH_SETTINGS, + "workfile_builder": { + "create_first_version": False, + "custom_templates": [] + } +} diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py new file mode 100644 index 0000000000..6bc72b4072 --- /dev/null +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -0,0 +1,221 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +create_flatten_image_enum = [ + {"value": "flatten_with_images", "label": "Flatten with images"}, + {"value": "flatten_only", "label": "Flatten only"}, + {"value": "no", "label": "No"}, +] + + +color_code_enum = [ + {"value": "red", "label": "Red"}, + {"value": "orange", "label": "Orange"}, + {"value": "yellowColor", "label": "Yellow"}, + {"value": "grain", "label": "Green"}, + {"value": "blue", "label": "Blue"}, + {"value": "violet", "label": "Violet"}, + {"value": "gray", "label": "Gray"}, +] + + +class ColorCodeMappings(BaseSettingsModel): + color_code: list[str] = Field( + title="Color codes for layers", + default_factory=list, + enum_resolver=lambda: color_code_enum, + ) + + layer_name_regex: list[str] = Field( + "", + title="Layer name regex" + ) + + product_type: str = Field( + "", + title="Resulting product type" + ) + + product_name_template: str = Field( + "", + title="Product name template" + ) + + +class ExtractedOptions(BaseSettingsModel): + tags: list[str] = Field( + title="Tags", + default_factory=list + ) + + +class CollectColorCodedInstancesPlugin(BaseSettingsModel): + """Set color for publishable layers, set its resulting product type + and template for product name. \n Can create flatten image from published + instances. + (Applicable only for remote publishing!)""" + + enabled: bool = Field(True, title="Enabled") + create_flatten_image: str = Field( + "", + title="Create flatten image", + enum_resolver=lambda: create_flatten_image_enum, + ) + + flatten_product_type_template: str = Field( + "", + title="Subset template for flatten image" + ) + + color_code_mapping: list[ColorCodeMappings] = Field( + title="Color code mappings", + default_factory=ColorCodeMappings, + ) + + +class CollectReviewPlugin(BaseSettingsModel): + """Should review product be created""" + enabled: bool = Field(True, title="Enabled") + + +class CollectVersionPlugin(BaseSettingsModel): + """Synchronize version for image and review instances by workfile version""" # noqa + enabled: bool = Field(True, title="Enabled") + + +class ValidateContainersPlugin(BaseSettingsModel): + """Check that workfile contains latest version of loaded items""" # noqa + _isGroup = True + enabled: bool = True + optional: bool = Field(False, title="Optional") + active: bool = Field(True, title="Active") + + +class ValidateNamingPlugin(BaseSettingsModel): + """Validate naming of products and layers""" # noqa + invalid_chars: str = Field( + '', + title="Regex pattern of invalid characters" + ) + + replace_char: str = Field( + '', + title="Replacement character" + ) + + +class ExtractImagePlugin(BaseSettingsModel): + """Currently only jpg and png are supported""" + formats: list[str] = Field( + title="Extract Formats", + default_factory=list, + ) + + +class ExtractReviewPlugin(BaseSettingsModel): + make_image_sequence: bool = Field( + False, + title="Make an image sequence instead of flatten image" + ) + + max_downscale_size: int = Field( + 8192, + title="Maximum size of sources for review", + description="FFMpeg can only handle limited resolution for creation of review and/or thumbnail", # noqa + gt=300, # greater than + le=16384, # less or equal + ) + + jpg_options: ExtractedOptions = Field( + title="Extracted jpg Options", + default_factory=ExtractedOptions + ) + + mov_options: ExtractedOptions = Field( + title="Extracted mov Options", + default_factory=ExtractedOptions + ) + + +class PhotoshopPublishPlugins(BaseSettingsModel): + CollectColorCodedInstances: CollectColorCodedInstancesPlugin = Field( + title="Collect Color Coded Instances", + default_factory=CollectColorCodedInstancesPlugin, + ) + CollectReview: CollectReviewPlugin = Field( + title="Collect Review", + default_factory=CollectReviewPlugin, + ) + + CollectVersion: CollectVersionPlugin = Field( + title="Create Image", + default_factory=CollectVersionPlugin, + ) + + ValidateContainers: ValidateContainersPlugin = Field( + title="Validate Containers", + default_factory=ValidateContainersPlugin, + ) + + ValidateNaming: ValidateNamingPlugin = Field( + title="Validate naming of products and layers", + default_factory=ValidateNamingPlugin, + ) + + ExtractImage: ExtractImagePlugin = Field( + title="Extract Image", + default_factory=ExtractImagePlugin, + ) + + ExtractReview: ExtractReviewPlugin = Field( + title="Extract Review", + default_factory=ExtractReviewPlugin, + ) + + +DEFAULT_PUBLISH_SETTINGS = { + "CollectColorCodedInstances": { + "create_flatten_image": "no", + "flatten_product_type_template": "", + "color_code_mapping": [] + }, + "CollectReview": { + "enabled": True + }, + "CollectVersion": { + "enabled": False + }, + "ValidateContainers": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateNaming": { + "invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,;]", + "replace_char": "_" + }, + "ExtractImage": { + "formats": [ + "png", + "jpg" + ] + }, + "ExtractReview": { + "make_image_sequence": False, + "max_downscale_size": 8192, + "jpg_options": { + "tags": [ + "review", + "ftrackreview" + ] + }, + "mov_options": { + "tags": [ + "review", + "ftrackreview" + ] + } + } +} diff --git a/server_addon/photoshop/server/settings/workfile_builder.py b/server_addon/photoshop/server/settings/workfile_builder.py new file mode 100644 index 0000000000..ec2ee136ad --- /dev/null +++ b/server_addon/photoshop/server/settings/workfile_builder.py @@ -0,0 +1,41 @@ +from pydantic import Field +from pathlib import Path + +from ayon_server.settings import BaseSettingsModel + + +class PathsTemplate(BaseSettingsModel): + windows: Path = Field( + '', + title="Windows" + ) + darwin: Path = Field( + '', + title="MacOS" + ) + linux: Path = Field( + '', + title="Linux" + ) + + +class CustomBuilderTemplate(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + ) + template_path: PathsTemplate = Field( + default_factory=PathsTemplate + ) + + +class WorkfileBuilderPlugin(BaseSettingsModel): + _title = "Workfile Builder" + create_first_version: bool = Field( + False, + title="Create first workfile" + ) + + custom_templates: list[CustomBuilderTemplate] = Field( + default_factory=CustomBuilderTemplate + ) diff --git a/server_addon/photoshop/server/version.py b/server_addon/photoshop/server/version.py new file mode 100644 index 0000000000..d4b9e2d7f3 --- /dev/null +++ b/server_addon/photoshop/server/version.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +"""Package declaring addon version.""" +__version__ = "0.1.0" diff --git a/server_addon/resolve/server/__init__.py b/server_addon/resolve/server/__init__.py new file mode 100644 index 0000000000..a84180d0f5 --- /dev/null +++ b/server_addon/resolve/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import ResolveSettings, DEFAULT_VALUES + + +class ResolveAddon(BaseServerAddon): + name = "resolve" + title = "DaVinci Resolve" + version = __version__ + settings_model: Type[ResolveSettings] = ResolveSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/resolve/server/imageio.py b/server_addon/resolve/server/imageio.py new file mode 100644 index 0000000000..c2bfcd40d0 --- /dev/null +++ b/server_addon/resolve/server/imageio.py @@ -0,0 +1,64 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class ImageIORemappingRulesModel(BaseSettingsModel): + host_native_name: str = Field( + title="Application native colorspace name" + ) + ocio_name: str = Field(title="OCIO colorspace name") + + +class ImageIORemappingModel(BaseSettingsModel): + rules: list[ImageIORemappingRulesModel] = Field( + default_factory=list) + + +class ResolveImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + remapping: ImageIORemappingModel = Field( + title="Remapping colorspace names", + default_factory=ImageIORemappingModel + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/resolve/server/settings.py b/server_addon/resolve/server/settings.py new file mode 100644 index 0000000000..326f6bea1e --- /dev/null +++ b/server_addon/resolve/server/settings.py @@ -0,0 +1,114 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + +from .imageio import ResolveImageIOModel + + +class CreateShotClipModels(BaseSettingsModel): + hierarchy: str = Field( + "{folder}/{sequence}", + title="Shot parent hierarchy", + section="Shot Hierarchy And Rename Settings" + ) + clipRename: bool = Field( + True, + title="Rename clips" + ) + clipName: str = Field( + "{track}{sequence}{shot}", + title="Clip name template" + ) + countFrom: int = Field( + 10, + title="Count sequence from" + ) + countSteps: int = Field( + 10, + title="Stepping number" + ) + + folder: str = Field( + "shots", + title="{folder}", + section="Shot Template Keywords" + ) + episode: str = Field( + "ep01", + title="{episode}" + ) + sequence: str = Field( + "sq01", + title="{sequence}" + ) + track: str = Field( + "{_track_}", + title="{track}" + ) + shot: str = Field( + "sh###", + title="{shot}" + ) + + vSyncOn: bool = Field( + False, + title="Enable Vertical Sync", + section="Vertical Synchronization Of Attributes" + ) + + workfileFrameStart: int = Field( + 1001, + title="Workfiles Start Frame", + section="Shot Attributes" + ) + handleStart: int = Field( + 10, + title="Handle start (head)" + ) + handleEnd: int = Field( + 10, + title="Handle end (tail)" + ) + + +class CreatorPuginsModel(BaseSettingsModel): + CreateShotClip: CreateShotClipModels = Field( + default_factory=CreateShotClipModels, + title="Create Shot Clip" + ) + + +class ResolveSettings(BaseSettingsModel): + launch_openpype_menu_on_start: bool = Field( + False, title="Launch OpenPype menu on start of Resolve" + ) + imageio: ResolveImageIOModel = Field( + default_factory=ResolveImageIOModel, + title="Color Management (ImageIO)" + ) + create: CreatorPuginsModel = Field( + default_factory=CreatorPuginsModel, + title="Creator plugins", + ) + + +DEFAULT_VALUES = { + "launch_openpype_menu_on_start": False, + "create": { + "CreateShotClip": { + "hierarchy": "{folder}/{sequence}", + "clipRename": True, + "clipName": "{track}{sequence}{shot}", + "countFrom": 10, + "countSteps": 10, + "folder": "shots", + "episode": "ep01", + "sequence": "sq01", + "track": "{_track_}", + "shot": "sh###", + "vSyncOn": False, + "workfileFrameStart": 1001, + "handleStart": 10, + "handleEnd": 10 + } + } +} diff --git a/server_addon/resolve/server/version.py b/server_addon/resolve/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/resolve/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/royal_render/server/__init__.py b/server_addon/royal_render/server/__init__.py new file mode 100644 index 0000000000..c5f0aafa00 --- /dev/null +++ b/server_addon/royal_render/server/__init__.py @@ -0,0 +1,17 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import RoyalRenderSettings, DEFAULT_VALUES + + +class RoyalRenderAddon(BaseServerAddon): + name = "royalrender" + version = __version__ + title = "Royal Render" + settings_model: Type[RoyalRenderSettings] = RoyalRenderSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/royal_render/server/settings.py b/server_addon/royal_render/server/settings.py new file mode 100644 index 0000000000..8b1fde6493 --- /dev/null +++ b/server_addon/royal_render/server/settings.py @@ -0,0 +1,53 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel + + +class ServerListSubmodel(BaseSettingsModel): + _layout = "compact" + name: str = Field("", title="Name") + value: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel + ) + + +class CollectSequencesFromJobModel(BaseSettingsModel): + review: bool = Field(True, title="Generate reviews from sequences") + + +class PublishPluginsModel(BaseSettingsModel): + CollectSequencesFromJob: CollectSequencesFromJobModel = Field( + default_factory=CollectSequencesFromJobModel, + title="Collect Sequences from the Job" + ) + + +class RoyalRenderSettings(BaseSettingsModel): + enabled: bool = True + rr_paths: list[ServerListSubmodel] = Field( + default_factory=list, + title="Royal Render Root Paths", + ) + publish: PublishPluginsModel = Field( + default_factory=PublishPluginsModel, + title="Publish plugins" + ) + + +DEFAULT_VALUES = { + "enabled": False, + "rr_paths": [ + { + "name": "default", + "value": { + "windows": "", + "darwin": "", + "linux": "" + } + } + ], + "publish": { + "CollectSequencesFromJob": { + "review": True + } + } +} diff --git a/server_addon/royal_render/server/version.py b/server_addon/royal_render/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/royal_render/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/timers_manager/server/__init__.py b/server_addon/timers_manager/server/__init__.py new file mode 100644 index 0000000000..29f9d47370 --- /dev/null +++ b/server_addon/timers_manager/server/__init__.py @@ -0,0 +1,13 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import TimersManagerSettings + + +class TimersManagerAddon(BaseServerAddon): + name = "timers_manager" + version = __version__ + title = "Timers Manager" + settings_model: Type[TimersManagerSettings] = TimersManagerSettings diff --git a/server_addon/timers_manager/server/settings.py b/server_addon/timers_manager/server/settings.py new file mode 100644 index 0000000000..27dbc6ef8e --- /dev/null +++ b/server_addon/timers_manager/server/settings.py @@ -0,0 +1,9 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class TimersManagerSettings(BaseSettingsModel): + auto_stop: bool = Field(True, title="Auto stop timer") + full_time: int = Field(15, title="Max idle time") + message_time: float = Field(0.5, title="When dialog will show") + disregard_publishing: bool = Field(False, title="Disregard publishing") diff --git a/server_addon/timers_manager/server/version.py b/server_addon/timers_manager/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/timers_manager/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/traypublisher/server/LICENSE b/server_addon/traypublisher/server/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/server_addon/traypublisher/server/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/server_addon/traypublisher/server/README.md b/server_addon/traypublisher/server/README.md new file mode 100644 index 0000000000..c0029bc782 --- /dev/null +++ b/server_addon/traypublisher/server/README.md @@ -0,0 +1,4 @@ +Photoshp Addon +=============== + +Integration with Adobe Traypublisher. diff --git a/server_addon/traypublisher/server/__init__.py b/server_addon/traypublisher/server/__init__.py new file mode 100644 index 0000000000..308f32069f --- /dev/null +++ b/server_addon/traypublisher/server/__init__.py @@ -0,0 +1,15 @@ +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import TraypublisherSettings, DEFAULT_TRAYPUBLISHER_SETTING + + +class Traypublisher(BaseServerAddon): + name = "traypublisher" + version = __version__ + + settings_model = TraypublisherSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_TRAYPUBLISHER_SETTING) diff --git a/server_addon/traypublisher/server/settings/__init__.py b/server_addon/traypublisher/server/settings/__init__.py new file mode 100644 index 0000000000..bcf8beffa7 --- /dev/null +++ b/server_addon/traypublisher/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + TraypublisherSettings, + DEFAULT_TRAYPUBLISHER_SETTING, +) + + +__all__ = ( + "TraypublisherSettings", + "DEFAULT_TRAYPUBLISHER_SETTING", +) diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py new file mode 100644 index 0000000000..345cb92e63 --- /dev/null +++ b/server_addon/traypublisher/server/settings/creator_plugins.py @@ -0,0 +1,46 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class BatchMovieCreatorPlugin(BaseSettingsModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + default_variants: list[str] = Field( + title="Default variants", + default_factory=list + ) + + default_tasks: list[str] = Field( + title="Default tasks", + default_factory=list + ) + + extensions: list[str] = Field( + title="Extensions", + default_factory=list + ) + + +class TrayPublisherCreatePluginsModel(BaseSettingsModel): + BatchMovieCreator: BatchMovieCreatorPlugin = Field( + title="Batch Movie Creator", + default_factory=BatchMovieCreatorPlugin + ) + + +DEFAULT_CREATORS = { + "BatchMovieCreator": { + "default_variants": [ + "Main" + ], + "default_tasks": [ + "Compositing" + ], + "extensions": [ + ".mov" + ] + }, +} diff --git a/server_addon/traypublisher/server/settings/editorial_creators.py b/server_addon/traypublisher/server/settings/editorial_creators.py new file mode 100644 index 0000000000..4111f22576 --- /dev/null +++ b/server_addon/traypublisher/server/settings/editorial_creators.py @@ -0,0 +1,181 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel, task_types_enum + + +class ClipNameTokenizerItem(BaseSettingsModel): + _layout = "expanded" + # TODO was 'dict-modifiable', is list of dicts now, must be fixed in code + name: str = Field("#TODO", title="Tokenizer name") + regex: str = Field("", title="Tokenizer regex") + + +class ShotAddTasksItem(BaseSettingsModel): + _layout = "expanded" + # TODO was 'dict-modifiable', is list of dicts now, must be fixed in code + name: str = Field('', title="Key") + task_type: list[str] = Field( + title="Task type", + default_factory=list, + enum_resolver=task_types_enum) + + +class ShotRenameSubmodel(BaseSettingsModel): + enabled: bool = True + shot_rename_template: str = Field( + "", + title="Shot rename template" + ) + + +parent_type_enum = [ + {"value": "Project", "label": "Project"}, + {"value": "Folder", "label": "Folder"}, + {"value": "Episode", "label": "Episode"}, + {"value": "Sequence", "label": "Sequence"}, +] + + +class TokenToParentConvertorItem(BaseSettingsModel): + # TODO - was 'type' must be renamed in code to `parent_type` + parent_type: str = Field( + "Project", + enum_resolver=lambda: parent_type_enum + ) + name: str = Field( + "", + title="Parent token name", + description="Unique name used in `Parent path template`" + ) + value: str = Field( + "", + title="Parent token value", + description="Template where any text, Anatomy keys and Tokens could be used" # noqa + ) + + +class ShotHierchySubmodel(BaseSettingsModel): + enabled: bool = True + parents_path: str = Field( + "", + title="Parents path template", + description="Using keys from \"Token to parent convertor\" or tokens directly" # noqa + ) + parents: list[TokenToParentConvertorItem] = Field( + default_factory=TokenToParentConvertorItem, + title="Token to parent convertor" + ) + + +output_file_type = [ + {"value": ".mp4", "label": "MP4"}, + {"value": ".mov", "label": "MOV"}, + {"value": ".wav", "label": "WAV"} +] + + +class ProductTypePresetItem(BaseSettingsModel): + product_type: str = Field("", title="Product type") + # TODO add placeholder '< Inherited >' + variant: str = Field("", title="Variant") + review: bool = Field(True, title="Review") + output_file_type: str = Field( + ".mp4", + enum_resolver=lambda: output_file_type + ) + + +class EditorialSimpleCreatorPlugin(BaseSettingsModel): + default_variants: list[str] = Field( + default_factory=list, + title="Default Variants" + ) + clip_name_tokenizer: list[ClipNameTokenizerItem] = Field( + default_factory=ClipNameTokenizerItem, + description=( + "Using Regex expression to create tokens. \nThose can be used" + " later in \"Shot rename\" creator \nor \"Shot hierarchy\"." + "\n\nTokens should be decorated with \"_\" on each side" + ) + ) + shot_rename: ShotRenameSubmodel = Field( + title="Shot Rename", + default_factory=ShotRenameSubmodel + ) + shot_hierarchy: ShotHierchySubmodel = Field( + title="Shot Hierarchy", + default_factory=ShotHierchySubmodel + ) + shot_add_tasks: list[ShotAddTasksItem] = Field( + title="Add tasks to shot", + default_factory=ShotAddTasksItem + ) + product_type_presets: list[ProductTypePresetItem] = Field( + default_factory=list + ) + + +class TraypublisherEditorialCreatorPlugins(BaseSettingsModel): + editorial_simple: EditorialSimpleCreatorPlugin = Field( + title="Editorial simple creator", + default_factory=EditorialSimpleCreatorPlugin, + ) + + +DEFAULT_EDITORIAL_CREATORS = { + "editorial_simple": { + "default_variants": [ + "Main" + ], + "clip_name_tokenizer": [ + {"name": "_sequence_", "regex": "(sc\\d{3})"}, + {"name": "_shot_", "regex": "(sh\\d{3})"} + ], + "shot_rename": { + "enabled": True, + "shot_rename_template": "{project[code]}_{_sequence_}_{_shot_}" + }, + "shot_hierarchy": { + "enabled": True, + "parents_path": "{project}/{folder}/{sequence}", + "parents": [ + { + "parent_type": "Project", + "name": "project", + "value": "{project[name]}" + }, + { + "parent_type": "Folder", + "name": "folder", + "value": "shots" + }, + { + "parent_type": "Sequence", + "name": "sequence", + "value": "{_sequence_}" + } + ] + }, + "shot_add_tasks": [], + "product_type_presets": [ + { + "product_type": "review", + "variant": "Reference", + "review": True, + "output_file_type": ".mp4" + }, + { + "product_type": "plate", + "variant": "", + "review": False, + "output_file_type": ".mov" + }, + { + "product_type": "audio", + "variant": "", + "review": False, + "output_file_type": ".wav" + } + ] + } +} diff --git a/server_addon/traypublisher/server/settings/imageio.py b/server_addon/traypublisher/server/settings/imageio.py new file mode 100644 index 0000000000..3df0d2f2fb --- /dev/null +++ b/server_addon/traypublisher/server/settings/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class TrayPublisherImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/traypublisher/server/settings/main.py b/server_addon/traypublisher/server/settings/main.py new file mode 100644 index 0000000000..fad96bef2f --- /dev/null +++ b/server_addon/traypublisher/server/settings/main.py @@ -0,0 +1,52 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + +from .imageio import TrayPublisherImageIOModel +from .simple_creators import ( + SimpleCreatorPlugin, + DEFAULT_SIMPLE_CREATORS, +) +from .editorial_creators import ( + TraypublisherEditorialCreatorPlugins, + DEFAULT_EDITORIAL_CREATORS, +) +from .creator_plugins import ( + TrayPublisherCreatePluginsModel, + DEFAULT_CREATORS, +) +from .publish_plugins import ( + TrayPublisherPublishPlugins, + DEFAULT_PUBLISH_PLUGINS, +) + + +class TraypublisherSettings(BaseSettingsModel): + """Traypublisher Project Settings.""" + imageio: TrayPublisherImageIOModel = Field( + default_factory=TrayPublisherImageIOModel, + title="Color Management (ImageIO)" + ) + simple_creators: list[SimpleCreatorPlugin] = Field( + title="Simple Create Plugins", + default_factory=SimpleCreatorPlugin, + ) + editorial_creators: TraypublisherEditorialCreatorPlugins = Field( + title="Editorial Creators", + default_factory=TraypublisherEditorialCreatorPlugins, + ) + create: TrayPublisherCreatePluginsModel = Field( + title="Create", + default_factory=TrayPublisherCreatePluginsModel + ) + publish: TrayPublisherPublishPlugins = Field( + title="Publish Plugins", + default_factory=TrayPublisherPublishPlugins + ) + + +DEFAULT_TRAYPUBLISHER_SETTING = { + "simple_creators": DEFAULT_SIMPLE_CREATORS, + "editorial_creators": DEFAULT_EDITORIAL_CREATORS, + "create": DEFAULT_CREATORS, + "publish": DEFAULT_PUBLISH_PLUGINS, +} diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py new file mode 100644 index 0000000000..3f00f3d52e --- /dev/null +++ b/server_addon/traypublisher/server/settings/publish_plugins.py @@ -0,0 +1,41 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class ValidatePluginModel(BaseSettingsModel): + _isGroup = True + enabled: bool = True + optional: bool = Field(True, title="Optional") + active: bool = Field(True, title="Active") + + +class ValidateFrameRangeModel(ValidatePluginModel): + """Allows to publish multiple video files in one go.
Name of matching + asset is parsed from file names ('asset.mov', 'asset_v001.mov', + 'my_asset_to_publish.mov')""" + + +class TrayPublisherPublishPlugins(BaseSettingsModel): + ValidateFrameRange: ValidateFrameRangeModel = Field( + title="Validate Frame Range", + default_factory=ValidateFrameRangeModel, + ) + ValidateExistingVersion: ValidatePluginModel = Field( + title="Validate Existing Version", + default_factory=ValidatePluginModel, + ) + + +DEFAULT_PUBLISH_PLUGINS = { + "ValidateFrameRange": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateExistingVersion": { + "enabled": True, + "optional": True, + "active": True + } +} diff --git a/server_addon/traypublisher/server/settings/simple_creators.py b/server_addon/traypublisher/server/settings/simple_creators.py new file mode 100644 index 0000000000..94d6602738 --- /dev/null +++ b/server_addon/traypublisher/server/settings/simple_creators.py @@ -0,0 +1,292 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class SimpleCreatorPlugin(BaseSettingsModel): + _layout = "expanded" + product_type: str = Field("", title="Product type") + # TODO add placeholder + identifier: str = Field("", title="Identifier") + label: str = Field("", title="Label") + icon: str = Field("", title="Icon") + default_variants: list[str] = Field( + default_factory=list, + title="Default Variants" + ) + description: str = Field( + "", + title="Description", + widget="textarea" + ) + detailed_description: str = Field( + "", + title="Detailed Description", + widget="textarea" + ) + allow_sequences: bool = Field( + False, + title="Allow sequences" + ) + allow_multiple_items: bool = Field( + False, + title="Allow multiple items" + ) + allow_version_control: bool = Field( + False, + title="Allow version control" + ) + extensions: list[str] = Field( + default_factory=list, + title="Extensions" + ) + + +DEFAULT_SIMPLE_CREATORS = [ + { + "product_type": "workfile", + "identifier": "", + "label": "Workfile", + "icon": "fa.file", + "default_variants": [ + "Main" + ], + "description": "Backup of a working scene", + "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.", + "allow_sequences": False, + "allow_multiple_items": False, + "allow_version_control": False, + "extensions": [ + ".ma", + ".mb", + ".nk", + ".hrox", + ".hip", + ".hiplc", + ".hipnc", + ".blend", + ".scn", + ".tvpp", + ".comp", + ".zip", + ".prproj", + ".drp", + ".psd", + ".psb", + ".aep" + ] + }, + { + "product_type": "model", + "identifier": "", + "label": "Model", + "icon": "fa.cubes", + "default_variants": [ + "Main", + "Proxy", + "Sculpt" + ], + "description": "Clean models", + "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ", + "allow_sequences": False, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".ma", + ".mb", + ".obj", + ".abc", + ".fbx", + ".bgeo", + ".bgeogz", + ".bgeosc", + ".usd", + ".blend" + ] + }, + { + "product_type": "pointcache", + "identifier": "", + "label": "Pointcache", + "icon": "fa.gears", + "default_variants": [ + "Main" + ], + "description": "Geometry Caches", + "detailed_description": "Alembic or bgeo cache of animated data", + "allow_sequences": True, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".abc", + ".bgeo", + ".bgeogz", + ".bgeosc" + ] + }, + { + "product_type": "plate", + "identifier": "", + "label": "Plate", + "icon": "mdi.camera-image", + "default_variants": [ + "Main", + "BG", + "Animatic", + "Reference", + "Offline" + ], + "description": "Footage Plates", + "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.", + "allow_sequences": True, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".exr", + ".png", + ".dpx", + ".jpg", + ".tiff", + ".tif", + ".mov", + ".mp4", + ".avi" + ] + }, + { + "product_type": "render", + "identifier": "", + "label": "Render", + "icon": "mdi.folder-multiple-image", + "default_variants": [], + "description": "Rendered images or video", + "detailed_description": "Sequence or single file renders", + "allow_sequences": True, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".exr", + ".png", + ".dpx", + ".jpg", + ".jpeg", + ".tiff", + ".tif", + ".mov", + ".mp4", + ".avi" + ] + }, + { + "product_type": "camera", + "identifier": "", + "label": "Camera", + "icon": "fa.video-camera", + "default_variants": [], + "description": "3d Camera", + "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.", + "allow_sequences": False, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".abc", + ".ma", + ".hip", + ".blend", + ".fbx", + ".usd" + ] + }, + { + "product_type": "image", + "identifier": "", + "label": "Image", + "icon": "fa.image", + "default_variants": [ + "Reference", + "Texture", + "Concept", + "Background" + ], + "description": "Single image", + "detailed_description": "Any image data can be published as image product type. References, textures, concept art, matte paints. This is a fallback 2d product type for everything that doesn't fit more specific product type.", + "allow_sequences": False, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".exr", + ".jpg", + ".jpeg", + ".dpx", + ".bmp", + ".tif", + ".tiff", + ".png", + ".psb", + ".psd" + ] + }, + { + "product_type": "vdb", + "identifier": "", + "label": "VDB Volumes", + "icon": "fa.cloud", + "default_variants": [], + "description": "Sparse volumetric data", + "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids", + "allow_sequences": True, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [ + ".vdb" + ] + }, + { + "product_type": "matchmove", + "identifier": "", + "label": "Matchmove", + "icon": "fa.empire", + "default_variants": [ + "Camera", + "Object", + "Mocap" + ], + "description": "Matchmoving script", + "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data", + "allow_sequences": False, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [] + }, + { + "product_type": "rig", + "identifier": "", + "label": "Rig", + "icon": "fa.wheelchair", + "default_variants": [], + "description": "CG rig file", + "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t", + "allow_sequences": False, + "allow_multiple_items": False, + "allow_version_control": False, + "extensions": [ + ".ma", + ".blend", + ".hip", + ".hda" + ] + }, + { + "product_type": "simpleUnrealTexture", + "identifier": "", + "label": "Simple UE texture", + "icon": "fa.image", + "default_variants": [], + "description": "Simple Unreal Engine texture", + "detailed_description": "Texture files with Unreal Engine naming conventions", + "allow_sequences": False, + "allow_multiple_items": True, + "allow_version_control": False, + "extensions": [] + } +] diff --git a/server_addon/traypublisher/server/version.py b/server_addon/traypublisher/server/version.py new file mode 100644 index 0000000000..a242f0e757 --- /dev/null +++ b/server_addon/traypublisher/server/version.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +"""Package declaring addon version.""" +__version__ = "0.1.1" diff --git a/server_addon/tvpaint/server/__init__.py b/server_addon/tvpaint/server/__init__.py new file mode 100644 index 0000000000..033d7d3792 --- /dev/null +++ b/server_addon/tvpaint/server/__init__.py @@ -0,0 +1,17 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import TvpaintSettings, DEFAULT_VALUES + + +class TvpaintAddon(BaseServerAddon): + name = "tvpaint" + title = "TVPaint" + version = __version__ + settings_model: Type[TvpaintSettings] = TvpaintSettings + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/tvpaint/server/settings/__init__.py b/server_addon/tvpaint/server/settings/__init__.py new file mode 100644 index 0000000000..abee32e897 --- /dev/null +++ b/server_addon/tvpaint/server/settings/__init__.py @@ -0,0 +1,10 @@ +from .main import ( + TvpaintSettings, + DEFAULT_VALUES, +) + + +__all__ = ( + "TvpaintSettings", + "DEFAULT_VALUES", +) diff --git a/server_addon/tvpaint/server/settings/create_plugins.py b/server_addon/tvpaint/server/settings/create_plugins.py new file mode 100644 index 0000000000..349bfdd288 --- /dev/null +++ b/server_addon/tvpaint/server/settings/create_plugins.py @@ -0,0 +1,133 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + + +class CreateWorkfileModel(BaseSettingsModel): + enabled: bool = Field(True) + default_variant: str = Field(title="Default variant") + default_variants: list[str] = Field( + default_factory=list, title="Default variants") + + +class CreateReviewModel(BaseSettingsModel): + enabled: bool = Field(True) + active_on_create: bool = Field(True, title="Active by default") + default_variant: str = Field(title="Default variant") + default_variants: list[str] = Field( + default_factory=list, title="Default variants") + + +class CreateRenderSceneModel(BaseSettingsModel): + enabled: bool = Field(True) + active_on_create: bool = Field(True, title="Active by default") + mark_for_review: bool = Field(True, title="Review by default") + default_pass_name: str = Field(title="Default beauty pass") + default_variant: str = Field(title="Default variant") + default_variants: list[str] = Field( + default_factory=list, title="Default variants") + + +class CreateRenderLayerModel(BaseSettingsModel): + mark_for_review: bool = Field(True, title="Review by default") + default_pass_name: str = Field(title="Default beauty pass") + default_variant: str = Field(title="Default variant") + default_variants: list[str] = Field( + default_factory=list, title="Default variants") + + +class CreateRenderPassModel(BaseSettingsModel): + mark_for_review: bool = Field(True, title="Review by default") + default_variant: str = Field(title="Default variant") + default_variants: list[str] = Field( + default_factory=list, title="Default variants") + + +class AutoDetectCreateRenderModel(BaseSettingsModel): + """The creator tries to auto-detect Render Layers and Render Passes in scene. + + For Render Layers is used group name as a variant and for Render Passes is + used TVPaint layer name. + + Group names can be renamed by their used order in scene. The renaming + template where can be used '{group_index}' formatting key which is + filled by "used position index of group". + - Template: 'L{group_index}' + - Group offset: '10' + - Group padding: '3' + + Would create group names "L010", "L020", ... + """ + + enabled: bool = Field(True) + allow_group_rename: bool = Field(title="Allow group rename") + group_name_template: str = Field(title="Group name template") + group_idx_offset: int = Field(1, title="Group index Offset", ge=1) + group_idx_padding: int = Field(4, title="Group index Padding", ge=1) + + +class CreatePluginsModel(BaseSettingsModel): + create_workfile: CreateWorkfileModel = Field( + default_factory=CreateWorkfileModel, + title="Create Workfile" + ) + create_review: CreateReviewModel = Field( + default_factory=CreateReviewModel, + title="Create Review" + ) + create_render_scene: CreateRenderSceneModel = Field( + default_factory=CreateReviewModel, + title="Create Render Scene" + ) + create_render_layer: CreateRenderLayerModel= Field( + default_factory=CreateRenderLayerModel, + title="Create Render Layer" + ) + create_render_pass: CreateRenderPassModel = Field( + default_factory=CreateRenderPassModel, + title="Create Render Pass" + ) + auto_detect_render: AutoDetectCreateRenderModel = Field( + default_factory=AutoDetectCreateRenderModel, + title="Auto-Detect Create Render", + ) + + +DEFAULT_CREATE_SETTINGS = { + "create_workfile": { + "enabled": True, + "default_variant": "Main", + "default_variants": [] + }, + "create_review": { + "enabled": True, + "active_on_create": True, + "default_variant": "Main", + "default_variants": [] + }, + "create_render_scene": { + "enabled": True, + "active_on_create": False, + "mark_for_review": True, + "default_pass_name": "beauty", + "default_variant": "Main", + "default_variants": [] + }, + "create_render_layer": { + "mark_for_review": False, + "default_pass_name": "beauty", + "default_variant": "Main", + "default_variants": [] + }, + "create_render_pass": { + "mark_for_review": False, + "default_variant": "Main", + "default_variants": [] + }, + "auto_detect_render": { + "enabled": False, + "allow_group_rename": True, + "group_name_template": "L{group_index}", + "group_idx_offset": 10, + "group_idx_padding": 3 + } +} diff --git a/server_addon/tvpaint/server/settings/filters.py b/server_addon/tvpaint/server/settings/filters.py new file mode 100644 index 0000000000..009febae06 --- /dev/null +++ b/server_addon/tvpaint/server/settings/filters.py @@ -0,0 +1,19 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel + + +class FiltersSubmodel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: str = Field( + "", + title="Textarea", + widget="textarea", + ) + + +class PublishFiltersModel(BaseSettingsModel): + env_search_replace_values: list[FiltersSubmodel] = Field( + default_factory=list + ) diff --git a/server_addon/tvpaint/server/settings/imageio.py b/server_addon/tvpaint/server/settings/imageio.py new file mode 100644 index 0000000000..50f8b7eef4 --- /dev/null +++ b/server_addon/tvpaint/server/settings/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class TVPaintImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/tvpaint/server/settings/main.py b/server_addon/tvpaint/server/settings/main.py new file mode 100644 index 0000000000..4cd6ac4b1a --- /dev/null +++ b/server_addon/tvpaint/server/settings/main.py @@ -0,0 +1,90 @@ +from pydantic import Field, validator +from ayon_server.settings import ( + BaseSettingsModel, + ensure_unique_names, +) + +from .imageio import TVPaintImageIOModel +from .workfile_builder import WorkfileBuilderPlugin +from .create_plugins import CreatePluginsModel, DEFAULT_CREATE_SETTINGS +from .publish_plugins import ( + PublishPluginsModel, + LoadPluginsModel, + DEFAULT_PUBLISH_SETTINGS, +) + + +class PublishGUIFilterItemModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: bool = Field(True, title="Active") + + +class PublishGUIFiltersModel(BaseSettingsModel): + _layout = "compact" + name: str = Field(title="Name") + value: list[PublishGUIFilterItemModel] = Field(default_factory=list) + + @validator("value") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class TvpaintSettings(BaseSettingsModel): + imageio: TVPaintImageIOModel = Field( + default_factory=TVPaintImageIOModel, + title="Color Management (ImageIO)" + ) + stop_timer_on_application_exit: bool = Field( + title="Stop timer on application exit") + create: CreatePluginsModel = Field( + default_factory=CreatePluginsModel, + title="Create plugins" + ) + publish: PublishPluginsModel = Field( + default_factory=PublishPluginsModel, + title="Publish plugins") + load: LoadPluginsModel = Field( + default_factory=LoadPluginsModel, + title="Load plugins") + workfile_builder: WorkfileBuilderPlugin = Field( + default_factory=WorkfileBuilderPlugin, + title="Workfile Builder" + ) + filters: list[PublishGUIFiltersModel] = Field( + default_factory=list, + title="Publish GUI Filters") + + @validator("filters") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +DEFAULT_VALUES = { + "stop_timer_on_application_exit": False, + "create": DEFAULT_CREATE_SETTINGS, + "publish": DEFAULT_PUBLISH_SETTINGS, + "load": { + "LoadImage": { + "defaults": { + "stretch": True, + "timestretch": True, + "preload": True + } + }, + "ImportImage": { + "defaults": { + "stretch": True, + "timestretch": True, + "preload": True + } + } + }, + "workfile_builder": { + "create_first_version": False, + "custom_templates": [] + }, + "filters": [] +} diff --git a/server_addon/tvpaint/server/settings/publish_plugins.py b/server_addon/tvpaint/server/settings/publish_plugins.py new file mode 100644 index 0000000000..76c7eaac01 --- /dev/null +++ b/server_addon/tvpaint/server/settings/publish_plugins.py @@ -0,0 +1,132 @@ +from pydantic import Field + +from ayon_server.settings import BaseSettingsModel +from ayon_server.types import ColorRGBA_uint8 + + +class CollectRenderInstancesModel(BaseSettingsModel): + ignore_render_pass_transparency: bool = Field( + title="Ignore Render Pass opacity" + ) + + +class ExtractSequenceModel(BaseSettingsModel): + """Review BG color is used for whole scene review and for thumbnails.""" + # TODO Use alpha color + review_bg: ColorRGBA_uint8 = Field( + (255, 255, 255, 1.0), + title="Review BG color") + + +class ValidatePluginModel(BaseSettingsModel): + enabled: bool = True + optional: bool = Field(True, title="Optional") + active: bool = Field(True, title="Active") + + +def compression_enum(): + return [ + {"value": "ZIP", "label": "ZIP"}, + {"value": "ZIPS", "label": "ZIPS"}, + {"value": "DWAA", "label": "DWAA"}, + {"value": "DWAB", "label": "DWAB"}, + {"value": "PIZ", "label": "PIZ"}, + {"value": "RLE", "label": "RLE"}, + {"value": "PXR24", "label": "PXR24"}, + {"value": "B44", "label": "B44"}, + {"value": "B44A", "label": "B44A"}, + {"value": "none", "label": "None"} + ] + + +class ExtractConvertToEXRModel(BaseSettingsModel): + """WARNING: This plugin does not work on MacOS (using OIIO tool).""" + enabled: bool = False + replace_pngs: bool = True + + exr_compression: str = Field( + "ZIP", + enum_resolver=compression_enum, + title="EXR Compression" + ) + + +class LoadImageDefaultModel(BaseSettingsModel): + _layout = "expanded" + stretch: bool = Field(title="Stretch") + timestretch: bool = Field(title="TimeStretch") + preload: bool = Field(title="Preload") + + +class LoadImageModel(BaseSettingsModel): + defaults: LoadImageDefaultModel = Field( + default_factory=LoadImageDefaultModel + ) + + +class PublishPluginsModel(BaseSettingsModel): + CollectRenderInstances: CollectRenderInstancesModel = Field( + default_factory=CollectRenderInstancesModel, + title="Collect Render Instances") + ExtractSequence: ExtractSequenceModel = Field( + default_factory=ExtractSequenceModel, + title="Extract Sequence") + ValidateProjectSettings: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Project Settings") + ValidateMarks: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate MarkIn/Out") + ValidateStartFrame: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Scene Start Frame") + ValidateAssetName: ValidatePluginModel = Field( + default_factory=ValidatePluginModel, + title="Validate Folder Name") + ExtractConvertToEXR: ExtractConvertToEXRModel = Field( + default_factory=ExtractConvertToEXRModel, + title="Extract Convert To EXR") + + +class LoadPluginsModel(BaseSettingsModel): + LoadImage: LoadImageModel = Field( + default_factory=LoadImageModel, + title="Load Image") + ImportImage: LoadImageModel = Field( + default_factory=LoadImageModel, + title="Import Image") + + +DEFAULT_PUBLISH_SETTINGS = { + "CollectRenderInstances": { + "ignore_render_pass_transparency": False + }, + "ExtractSequence": { + "review_bg": [255, 255, 255, 1.0] + }, + "ValidateProjectSettings": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateMarks": { + "enabled": True, + "optional": True, + "active": True + }, + "ValidateStartFrame": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateAssetName": { + "enabled": True, + "optional": True, + "active": True + }, + "ExtractConvertToEXR": { + "enabled": False, + "replace_pngs": True, + "exr_compression": "ZIP" + } +} diff --git a/server_addon/tvpaint/server/settings/workfile_builder.py b/server_addon/tvpaint/server/settings/workfile_builder.py new file mode 100644 index 0000000000..e0aba5da7e --- /dev/null +++ b/server_addon/tvpaint/server/settings/workfile_builder.py @@ -0,0 +1,30 @@ +from pydantic import Field + +from ayon_server.settings import ( + BaseSettingsModel, + MultiplatformPathModel, + task_types_enum, +) + + +class CustomBuilderTemplate(BaseSettingsModel): + task_types: list[str] = Field( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + template_path: MultiplatformPathModel = Field( + default_factory=MultiplatformPathModel + ) + + +class WorkfileBuilderPlugin(BaseSettingsModel): + _title = "Workfile Builder" + create_first_version: bool = Field( + False, + title="Create first workfile" + ) + + custom_templates: list[CustomBuilderTemplate] = Field( + default_factory=CustomBuilderTemplate + ) diff --git a/server_addon/tvpaint/server/version.py b/server_addon/tvpaint/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/tvpaint/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/server_addon/unreal/server/__init__.py b/server_addon/unreal/server/__init__.py new file mode 100644 index 0000000000..a5f3e9597d --- /dev/null +++ b/server_addon/unreal/server/__init__.py @@ -0,0 +1,19 @@ +from typing import Type + +from ayon_server.addons import BaseServerAddon + +from .version import __version__ +from .settings import UnrealSettings, DEFAULT_VALUES + + +class UnrealAddon(BaseServerAddon): + name = "unreal" + title = "Unreal" + version = __version__ + settings_model: Type[UnrealSettings] = UnrealSettings + frontend_scopes = {} + services = {} + + async def get_default_settings(self): + settings_model_cls = self.get_settings_model() + return settings_model_cls(**DEFAULT_VALUES) diff --git a/server_addon/unreal/server/imageio.py b/server_addon/unreal/server/imageio.py new file mode 100644 index 0000000000..dde042ba47 --- /dev/null +++ b/server_addon/unreal/server/imageio.py @@ -0,0 +1,48 @@ +from pydantic import Field, validator +from ayon_server.settings import BaseSettingsModel +from ayon_server.settings.validators import ensure_unique_names + + +class ImageIOConfigModel(BaseSettingsModel): + override_global_config: bool = Field( + False, + title="Override global OCIO config" + ) + filepath: list[str] = Field( + default_factory=list, + title="Config path" + ) + + +class ImageIOFileRuleModel(BaseSettingsModel): + name: str = Field("", title="Rule name") + pattern: str = Field("", title="Regex pattern") + colorspace: str = Field("", title="Colorspace name") + ext: str = Field("", title="File extension") + + +class ImageIOFileRulesModel(BaseSettingsModel): + activate_host_rules: bool = Field(False) + rules: list[ImageIOFileRuleModel] = Field( + default_factory=list, + title="Rules" + ) + + @validator("rules") + def validate_unique_outputs(cls, value): + ensure_unique_names(value) + return value + + +class UnrealImageIOModel(BaseSettingsModel): + activate_host_color_management: bool = Field( + True, title="Enable Color Management" + ) + ocio_config: ImageIOConfigModel = Field( + default_factory=ImageIOConfigModel, + title="OCIO config" + ) + file_rules: ImageIOFileRulesModel = Field( + default_factory=ImageIOFileRulesModel, + title="File Rules" + ) diff --git a/server_addon/unreal/server/settings.py b/server_addon/unreal/server/settings.py new file mode 100644 index 0000000000..479e041e25 --- /dev/null +++ b/server_addon/unreal/server/settings.py @@ -0,0 +1,64 @@ +from pydantic import Field +from ayon_server.settings import BaseSettingsModel + +from .imageio import UnrealImageIOModel + + +class ProjectSetup(BaseSettingsModel): + dev_mode: bool = Field( + False, + title="Dev mode" + ) + + +def _render_format_enum(): + return [ + {"value": "png", "label": "PNG"}, + {"value": "exr", "label": "EXR"}, + {"value": "jpg", "label": "JPG"}, + {"value": "bmp", "label": "BMP"} + ] + + +class UnrealSettings(BaseSettingsModel): + imageio: UnrealImageIOModel = Field( + default_factory=UnrealImageIOModel, + title="Color Management (ImageIO)" + ) + level_sequences_for_layouts: bool = Field( + False, + title="Generate level sequences when loading layouts" + ) + delete_unmatched_assets: bool = Field( + False, + title="Delete assets that are not matched" + ) + render_config_path: str = Field( + "", + title="Render Config Path" + ) + preroll_frames: int = Field( + 0, + title="Pre-roll frames" + ) + render_format: str = Field( + "png", + title="Render format", + enum_resolver=_render_format_enum + ) + project_setup: ProjectSetup = Field( + default_factory=ProjectSetup, + title="Project Setup", + ) + + +DEFAULT_VALUES = { + "level_sequences_for_layouts": False, + "delete_unmatched_assets": False, + "render_config_path": "", + "preroll_frames": 0, + "render_format": "png", + "project_setup": { + "dev_mode": False + } +} diff --git a/server_addon/unreal/server/version.py b/server_addon/unreal/server/version.py new file mode 100644 index 0000000000..3dc1f76bc6 --- /dev/null +++ b/server_addon/unreal/server/version.py @@ -0,0 +1 @@ +__version__ = "0.1.0"